<?php

if (!defined('ABSPATH')) {
    exit;
}

final class SPP_REST_Controller
{
    private const NAMESPACE = 'support-provisioning/v1';

    public function __construct(
        private SPP_Repository $repository,
        private SPP_Proxmox_Client $proxmox,
        private SPP_Expiration_Service $expiration_service,
        private SPP_Permissions $permissions
    ) {
    }

    public function register_hooks(): void
    {
        add_action('rest_api_init', [$this, 'register_routes']);
    }

    public function register_routes(): void
    {
        register_rest_route(self::NAMESPACE, '/templates', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'list_templates'],
            'permission_callback' => [$this, 'can_read'],
        ]);

        register_rest_route(self::NAMESPACE, '/quota', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_quota'],
            'permission_callback' => [$this, 'can_read'],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'list_deployments'],
                'permission_callback' => [$this, 'can_read'],
            ],
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'create_deployment'],
                'permission_callback' => [$this, 'can_create_deployments'],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_deployment'],
                'permission_callback' => [$this, 'can_read'],
            ],
            [
                'methods' => WP_REST_Server::DELETABLE,
                'callback' => [$this, 'delete_deployment'],
                'permission_callback' => [$this, 'can_delete_deployments'],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/shares', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'list_deployment_shares'],
                'permission_callback' => [$this, 'can_read'],
            ],
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'share_deployment'],
                'permission_callback' => [$this, 'can_read'],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/shares/(?P<user_id>\d+)', [
            'methods' => WP_REST_Server::DELETABLE,
            'callback' => [$this, 'unshare_deployment'],
            'permission_callback' => [$this, 'can_read'],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/start', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'start_deployment'],
            'permission_callback' => [$this, 'can_start_deployments'],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/stop', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'stop_deployment'],
            'permission_callback' => [$this, 'can_stop_deployments'],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/prolong', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'prolong_deployment'],
            'permission_callback' => [$this, 'can_prolong_deployments'],
        ]);

        register_rest_route(self::NAMESPACE, '/deployments/(?P<id>\d+)/refresh-ips', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'refresh_deployment_ips'],
            'permission_callback' => [$this, 'can_refresh_deployment_ips'],
        ]);
    }

    public function can_read(): bool
    {
        return is_user_logged_in() && $this->permissions->current_user_has(SPP_Permissions::VIEW_PORTAL);
    }

    public function can_create_deployments(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::CREATE_DEPLOYMENTS);
    }

    public function can_start_deployments(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::START_DEPLOYMENTS);
    }

    public function can_stop_deployments(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::STOP_DEPLOYMENTS);
    }

    public function can_prolong_deployments(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::PROLONG_DEPLOYMENTS);
    }

    public function can_refresh_deployment_ips(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::REFRESH_DEPLOYMENT_IPS);
    }

    public function can_delete_deployments(): bool
    {
        return $this->can_use_portal_action(SPP_Permissions::DELETE_DEPLOYMENTS);
    }

    private function can_use_portal_action(string $permission): bool
    {
        return $this->can_read() && $this->permissions->current_user_has($permission);
    }

    private function can_manage_all_deployments(): bool
    {
        return $this->permissions->current_user_has(SPP_Permissions::MANAGE_ALL_DEPLOYMENTS);
    }

    public function list_templates(): WP_REST_Response
    {
        $this->sync_expirations();

        return rest_ensure_response($this->repository->templates());
    }

    public function get_quota(): WP_REST_Response
    {
        $this->sync_expirations();

        return rest_ensure_response($this->repository->quota(get_current_user_id()));
    }

    public function list_deployments(): WP_REST_Response
    {
        $this->sync_expirations();

        return rest_ensure_response($this->repository->deployments(
            get_current_user_id(),
            $this->can_manage_all_deployments()
        ));
    }

    public function get_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $deployment = $this->repository->deployment_for_user(
            (int) $request['id'],
            get_current_user_id(),
            $this->can_manage_all_deployments()
        );

        if ($deployment === null) {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        return rest_ensure_response($this->deployment_response($deployment));
    }

    public function create_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $template_id = (int) $request->get_param('templateId');
        $name = sanitize_text_field((string) $request->get_param('name'));
        $ttl_hours = (int) $request->get_param('ttlHours');
        $never_expire = (bool) $request->get_param('neverExpire');

        if ($template_id < 1 || strlen($name) < 3 || strlen($name) > 160) {
            return new WP_Error('spp_invalid_request', 'Template and a deployment name of 3-160 characters are required.', ['status' => 400]);
        }

        $template = $this->repository->template($template_id);
        if ($template === null) {
            return new WP_Error('spp_template_not_found', 'Template not found.', ['status' => 404]);
        }

        if ($never_expire) {
            $ttl_hours = null;
        } elseif ($ttl_hours < 1) {
            $ttl_hours = (int) $template['default_ttl_hours'];
        }

        if ($ttl_hours !== null && $ttl_hours > 720) {
            return new WP_Error('spp_ttl_too_long', 'TTL cannot exceed 720 hours.', ['status' => 400]);
        }

        $quota_error = $this->validate_memory_quota((int) $template['memory_mb'], get_current_user_id());
        if ($quota_error instanceof WP_Error) {
            return $quota_error;
        }

        $provisioning_type = $this->normalise_template_type((string) ($template['provisioning_type'] ?? 'qemu'));

        try {
            $instance = $this->proxmox->provision_instance([
                'provisioning_type' => $provisioning_type,
                'template_vm_id' => (int) $template['proxmox_template_id'],
                'lxc_template_ref' => (string) ($template['proxmox_template_ref'] ?? ''),
                'name' => $name,
                'cpu_cores' => (int) $template['cpu_cores'],
                'memory_mb' => (int) $template['memory_mb'],
                'disk_gb' => (int) $template['disk_gb'],
                'tags' => $this->deployment_tags(wp_get_current_user()),
            ]);

            $vm_id = (int) $instance['vm_id'];
            $deployment = $this->repository->create_deployment(
                $template,
                $name,
                $ttl_hours,
                $vm_id,
                $this->safe_ip_addresses($provisioning_type, $vm_id),
                get_current_user_id()
            );
        } catch (Throwable $error) {
            return new WP_Error('spp_proxmox_error', $error->getMessage(), ['status' => 502]);
        }

        $deployment = $this->repository->deployment_for_user(
            (int) $deployment['id'],
            get_current_user_id(),
            $this->can_manage_all_deployments()
        );

        return new WP_REST_Response($this->deployment_response($deployment), 201);
    }

    private function validate_memory_quota(int $requested_memory_mb, int $actor_id): ?WP_Error
    {
        $quota = $this->repository->quota($actor_id);

        if ($quota['userLimitMb'] > 0 && ($quota['userUsedMb'] + $requested_memory_mb) > $quota['userLimitMb']) {
            return new WP_Error(
                'spp_user_quota_exceeded',
                'This deployment would exceed your RAM contingent.',
                ['status' => 403, 'quota' => $quota]
            );
        }

        if ($quota['globalLimitMb'] > 0 && ($quota['globalUsedMb'] + $requested_memory_mb) > $quota['globalLimitMb']) {
            return new WP_Error(
                'spp_global_quota_exceeded',
                'This deployment would exceed the global RAM contingent.',
                ['status' => 403, 'quota' => $quota]
            );
        }

        return null;
    }

    public function start_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        return $this->apply_lifecycle_action((int) $request['id'], 'RUNNING', 'DEPLOYMENT_STARTED', 'start');
    }

    public function stop_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        return $this->apply_lifecycle_action((int) $request['id'], 'STOPPED', 'DEPLOYMENT_STOPPED', 'stop');
    }

    public function delete_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        return $this->apply_lifecycle_action((int) $request['id'], 'DELETED', 'DEPLOYMENT_DELETED', 'delete');
    }

    public function prolong_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $id = (int) $request['id'];
        $record = $this->repository->deployment_record($id);

        if ($record === null || $record['status'] === 'DELETED') {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if (!$this->repository->user_can_access_deployment($id, get_current_user_id(), $this->can_manage_all_deployments())) {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        $ttl_hours = (int) $request->get_param('ttlHours');
        $never_expire = (bool) $request->get_param('neverExpire');
        $deployment = $this->repository->deployment_for_user($id, get_current_user_id(), $this->can_manage_all_deployments());

        if ($deployment === null) {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if ($never_expire) {
            $ttl_hours = null;
        } elseif ($ttl_hours < 1) {
            return new WP_Error('spp_invalid_ttl', 'Choose a TTL in hours or select never expire.', ['status' => 400]);
        }

        if ($ttl_hours !== null && $ttl_hours > 720) {
            return new WP_Error('spp_ttl_too_long', 'TTL cannot exceed 720 hours.', ['status' => 400]);
        }

        if ($record['status'] === 'EXPIRED') {
            $quota_error = $this->validate_memory_quota((int) $deployment['memoryMb'], (int) $record['requested_by']);
            if ($quota_error instanceof WP_Error) {
                return $quota_error;
            }
        }

        $this->repository->prolong_deployment($id, $ttl_hours, get_current_user_id());
        $deployment = $this->repository->deployment_for_user($id, get_current_user_id(), $this->can_manage_all_deployments());

        return rest_ensure_response($this->deployment_response($deployment));
    }

    public function refresh_deployment_ips(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $id = (int) $request['id'];
        $record = $this->repository->deployment_record($id);

        if ($record === null || $record['status'] === 'DELETED') {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if (!$this->repository->user_can_access_deployment($id, get_current_user_id(), $this->can_manage_all_deployments())) {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if (empty($record['proxmox_vm_id'])) {
            return new WP_Error('spp_missing_vm_id', 'Deployment is missing a Proxmox resource id.', ['status' => 409]);
        }

        try {
            $this->repository->update_deployment_ips(
                $id,
                $this->proxmox->get_ip_addresses(
                    (string) ($record['provisioning_type'] ?? 'qemu'),
                    (int) $record['proxmox_vm_id']
                ),
                get_current_user_id()
            );
        } catch (Throwable $error) {
            return new WP_Error('spp_proxmox_error', $error->getMessage(), ['status' => 502]);
        }

        $deployment = $this->repository->deployment_for_user($id, get_current_user_id(), $this->can_manage_all_deployments());

        return rest_ensure_response($this->deployment_response($deployment));
    }

    public function list_deployment_shares(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $id = (int) $request['id'];

        if (!$this->user_can_share_deployment($id)) {
            return new WP_Error('spp_forbidden', 'Only the owner or a deployment manager can view shares.', ['status' => 403]);
        }

        return rest_ensure_response($this->repository->deployment_shares($id));
    }

    public function share_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $id = (int) $request['id'];

        if (!$this->user_can_share_deployment($id)) {
            return new WP_Error('spp_forbidden', 'Only the owner or a deployment manager can share this deployment.', ['status' => 403]);
        }

        $identifier = sanitize_text_field((string) $request->get_param('user'));
        $target = $this->find_share_target_user($identifier);

        if (!$target instanceof WP_User) {
            return new WP_Error('spp_user_not_found', 'User not found.', ['status' => 404]);
        }

        if ((int) $target->ID === get_current_user_id()) {
            return new WP_Error('spp_invalid_share_target', 'You already have access to this deployment.', ['status' => 400]);
        }

        $record = $this->repository->deployment_record($id);
        if ($record === null || (int) $record['requested_by'] === (int) $target->ID) {
            return new WP_Error('spp_invalid_share_target', 'The owner already has access to this deployment.', ['status' => 400]);
        }

        if (!$this->permissions->user_has((int) $target->ID, SPP_Permissions::VIEW_PORTAL)) {
            return new WP_Error('spp_user_without_portal_access', 'That user does not have portal access yet.', ['status' => 400]);
        }

        $this->repository->share_deployment($id, (int) $target->ID, get_current_user_id());

        return rest_ensure_response($this->repository->deployment_shares($id));
    }

    public function unshare_deployment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $id = (int) $request['id'];

        if (!$this->user_can_share_deployment($id)) {
            return new WP_Error('spp_forbidden', 'Only the owner or a deployment manager can change shares.', ['status' => 403]);
        }

        $this->repository->unshare_deployment($id, (int) $request['user_id'], get_current_user_id());

        return rest_ensure_response($this->repository->deployment_shares($id));
    }

    private function apply_lifecycle_action(int $id, string $status, string $audit_action, string $method): WP_REST_Response|WP_Error
    {
        $this->sync_expirations();
        $record = $this->repository->deployment_record($id);

        if ($record === null || $record['status'] === 'DELETED') {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if (!$this->repository->user_can_access_deployment($id, get_current_user_id(), $this->can_manage_all_deployments())) {
            return new WP_Error('spp_not_found', 'Deployment not found.', ['status' => 404]);
        }

        if ($method === 'delete' && !$this->user_can_delete_deployment($id)) {
            return new WP_Error('spp_forbidden', 'Only the owner or a deployment manager can delete this deployment.', ['status' => 403]);
        }

        if ($method === 'start' && $record['status'] === 'EXPIRED') {
            return new WP_Error(
                'spp_expired_deployment',
                'This deployment is expired. Prolong its TTL before starting it again.',
                ['status' => 409]
            );
        }

        if (empty($record['proxmox_vm_id'])) {
            return new WP_Error('spp_missing_vm_id', 'Deployment is missing a Proxmox resource id.', ['status' => 409]);
        }

        $provisioning_type = $this->normalise_template_type((string) ($record['provisioning_type'] ?? 'qemu'));

        try {
            if ($method === 'start') {
                $this->proxmox->start_instance($provisioning_type, (int) $record['proxmox_vm_id']);
                $this->repository->update_deployment_status_and_ips(
                    $id,
                    $status,
                    $this->safe_ip_addresses($provisioning_type, (int) $record['proxmox_vm_id']),
                    $audit_action,
                    get_current_user_id()
                );
            } elseif ($method === 'stop') {
                $this->proxmox->stop_instance($provisioning_type, (int) $record['proxmox_vm_id']);
                $this->repository->update_deployment_status($id, $status, $audit_action, get_current_user_id());
            } else {
                $this->proxmox->delete_instance($provisioning_type, (int) $record['proxmox_vm_id']);
                $this->repository->update_deployment_status($id, $status, $audit_action, get_current_user_id());
            }
        } catch (Throwable $error) {
            return new WP_Error('spp_proxmox_error', $error->getMessage(), ['status' => 502]);
        }

        if ($method === 'delete') {
            return rest_ensure_response(['deleted' => true]);
        }

        $deployment = $this->repository->deployment_for_user($id, get_current_user_id(), $this->can_manage_all_deployments());

        return rest_ensure_response($this->deployment_response($deployment));
    }

    /**
     * @param array<string, mixed>|null $deployment
     * @return array<string, mixed>
     */
    private function deployment_response(?array $deployment): array
    {
        if ($deployment === null) {
            return [];
        }

        $id = (int) $deployment['id'];
        $deployment['canShare'] = $this->user_can_share_deployment($id);
        $deployment['canDelete'] = $this->user_can_delete_deployment($id);

        if ($deployment['canShare']) {
            $deployment['shares'] = $this->repository->deployment_shares($id);
        }

        return $deployment;
    }

    private function user_can_share_deployment(int $deployment_id): bool
    {
        $record = $this->repository->deployment_record($deployment_id);
        if ($record === null || $record['status'] === 'DELETED') {
            return false;
        }

        return $this->repository->user_owns_deployment($deployment_id, get_current_user_id())
            || $this->can_manage_all_deployments();
    }

    private function user_can_delete_deployment(int $deployment_id): bool
    {
        $record = $this->repository->deployment_record($deployment_id);
        if ($record === null || $record['status'] === 'DELETED') {
            return false;
        }

        return $this->permissions->current_user_has(SPP_Permissions::DELETE_DEPLOYMENTS)
            && (
                $this->repository->user_owns_deployment($deployment_id, get_current_user_id())
                || $this->can_manage_all_deployments()
            );
    }

    private function find_share_target_user(string $identifier): ?WP_User
    {
        if ($identifier === '') {
            return null;
        }

        if (is_email($identifier)) {
            $user = get_user_by('email', $identifier);
        } else {
            $user = get_user_by('login', $identifier);
        }

        return $user instanceof WP_User ? $user : null;
    }

    private function sync_expirations(): void
    {
        $this->expiration_service->expire_due_deployments();
    }

    /**
     * @return array<int, string>
     */
    private function safe_ip_addresses(string $provisioning_type, int $vm_id): array
    {
        try {
            return $this->proxmox->get_ip_addresses($provisioning_type, $vm_id);
        } catch (Throwable) {
            return [];
        }
    }

    /**
     * @return array<int, string>
     */
    private function deployment_tags(WP_User $user): array
    {
        $login = $user->user_login !== '' ? $user->user_login : $user->display_name;
        $user_tag = strtolower(sanitize_title($login));

        if ($user_tag === '') {
            $user_tag = (string) $user->ID;
        }

        return ['support-portal', 'user-' . $user_tag];
    }

    private function normalise_template_type(string $type): string
    {
        return strtolower($type) === 'lxc' ? 'lxc' : 'qemu';
    }
}
