<?php

namespace MintyPHP\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;

/** @api Called from pages/helpdesk/handovers action files */
class HandoverService
{
    /** @api Used in pages and templates for status checks */
    public const STATUS_DRAFT = 'draft';
    /** @api Used in pages and templates for status checks */
    public const STATUS_IN_PROGRESS = 'in_progress';
    /** @api Used in pages and templates for status checks */
    public const STATUS_COMPLETED = 'completed';
    /** @api Used in pages and templates for status checks */
    public const STATUS_ARCHIVED = 'archived';
    /** @api Submitted by assignee, awaiting manager review */
    public const STATUS_UNDER_REVIEW = 'under_review';

    /** @api Used in validation */
    public const ALL_STATUSES = [
        self::STATUS_DRAFT,
        self::STATUS_IN_PROGRESS,
        self::STATUS_UNDER_REVIEW,
        self::STATUS_COMPLETED,
        self::STATUS_ARCHIVED,
    ];

    public const PERMISSION_CREATE = 'create';
    public const PERMISSION_MANAGE = 'manage';

    /**
     * Status transitions allowed per permission level.
     * create-level: draft → in_progress → under_review.
     * manage-level: any status.
     */
    private const ALLOWED_STATUS_BY_PERMISSION = [
        self::PERMISSION_CREATE => [self::STATUS_DRAFT, self::STATUS_IN_PROGRESS, self::STATUS_UNDER_REVIEW],
        self::PERMISSION_MANAGE => [self::STATUS_DRAFT, self::STATUS_IN_PROGRESS, self::STATUS_UNDER_REVIEW, self::STATUS_COMPLETED, self::STATUS_ARCHIVED],
    ];

    /**
     * Statuses in which a create-level user may edit field values.
     */
    private const EDITABLE_STATUSES_CREATE = [self::STATUS_DRAFT, self::STATUS_IN_PROGRESS];

    public function __construct(
        private readonly HandoverRepository $repository,
        private readonly SoftwareProductService $softwareProductService,
        private readonly ?HandoverRevisionService $revisionService = null,
        private readonly ?HandoverNotificationService $notificationService = null,
    ) {
    }

    /**
     * Create a new handover with a schema snapshot from the software product.
     *
     * @return array{ok: bool, id: int|null, errors: array<string, string>}
     */
    /**
     * @param array<string, mixed> $fieldValues
     */
    public function create(
        int $tenantId,
        string $debitorNo,
        string $debitorName,
        string $productCode,
        string $domainNo,
        string $domainUrl,
        int $userId,
        array $fieldValues = [],
    ): array {
        $errors = [];

        $debitorNo = trim($debitorNo);
        if ($debitorNo === '') {
            $errors['debitor_no'] = t('Debitor number is required');
        }

        $productCode = trim($productCode);
        if ($productCode === '') {
            $errors['product_code'] = t('Software product is required');
        }

        $domainNo = trim($domainNo);
        if ($domainNo === '') {
            $errors['domain_no'] = t('Please select a domain');
        }

        if ($errors !== []) {
            return ['ok' => false, 'id' => null, 'errors' => $errors];
        }

        $product = $this->softwareProductService->findByCode($productCode);
        if ($product === null) {
            return ['ok' => false, 'id' => null, 'errors' => ['product_code' => t('Software product not found')]];
        }

        $schemaRaw = $product['handover_protocol_schema'] ?? null;
        if ($schemaRaw === null || $schemaRaw === '') {
            return ['ok' => false, 'id' => null, 'errors' => ['product_code' => t('Software product has no handover protocol schema')]];
        }

        $schema = json_decode($schemaRaw, true);
        if (!is_array($schema) || empty($schema['fields'])) {
            return ['ok' => false, 'id' => null, 'errors' => ['product_code' => t('Software product has no handover protocol schema')]];
        }

        // Validate field values against schema if provided
        if ($fieldValues !== []) {
            $validationErrors = $this->validateFieldValues($fieldValues, $schema['fields']);
            if ($validationErrors !== []) {
                return ['ok' => false, 'id' => null, 'errors' => $validationErrors];
            }
        }

        $fieldValuesJson = $fieldValues !== []
            ? json_encode($fieldValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            : json_encode(new \stdClass(), JSON_FORCE_OBJECT);

        $id = $this->repository->insert([
            'tenant_id' => $tenantId,
            'debitor_no' => $debitorNo,
            'debitor_name' => trim($debitorName),
            'product_code' => $productCode,
            'domain_no' => $domainNo,
            'domain_url' => trim($domainUrl),
            'status' => self::STATUS_DRAFT,
            'schema_snapshot' => $schemaRaw,
            'field_values' => $fieldValuesJson,
            'created_by' => $userId,
        ]);

        if ($id === null) {
            return ['ok' => false, 'id' => null, 'errors' => ['general' => t('Failed to create handover')]];
        }

        // Create initial revision (revision 1)
        $this->revisionService?->createRevision(
            $tenantId,
            $id,
            $fieldValues,
            self::STATUS_DRAFT,
            (int) ($schema['version'] ?? 1),
            $userId,
            HandoverRevisionService::CHANGE_TYPE_INITIAL
        );

        return ['ok' => true, 'id' => $id, 'errors' => []];
    }

    /**
     * Update field values for a handover.
     *
     * @param array<string, mixed> $fieldValues
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function updateFields(
        int $tenantId,
        int $id,
        array $fieldValues,
        int $userId,
        string $permissionLevel,
    ): array {
        $handover = $this->repository->findById($tenantId, $id);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        if (!$this->canEdit($handover['status'], $permissionLevel)) {
            return ['ok' => false, 'errors' => ['general' => t('You do not have permission to edit this handover')]];
        }

        $schema = json_decode((string) ($handover['schema_snapshot'] ?? '{}'), true);
        $schemaFields = is_array($schema) ? ($schema['fields'] ?? []) : [];

        $validationErrors = $this->validateFieldValues($fieldValues, $schemaFields);
        if ($validationErrors !== []) {
            return ['ok' => false, 'errors' => $validationErrors];
        }

        $json = json_encode($fieldValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $updated = $this->repository->updateFieldValues($tenantId, $id, $json, $userId);

        if (!$updated) {
            return ['ok' => false, 'errors' => ['general' => t('Failed to save')]];
        }

        return ['ok' => true, 'errors' => []];
    }

    /**
     * Change the status of a handover.
     *
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function changeStatus(
        int $tenantId,
        int $id,
        string $newStatus,
        int $userId,
        string $permissionLevel,
    ): array {
        if (!in_array($newStatus, self::ALL_STATUSES, true)) {
            return ['ok' => false, 'errors' => ['status' => t('Invalid status')]];
        }

        $handover = $this->repository->findById($tenantId, $id);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        $allowed = self::ALLOWED_STATUS_BY_PERMISSION[$permissionLevel] ?? [];
        if (!in_array($newStatus, $allowed, true)) {
            return ['ok' => false, 'errors' => ['status' => t('You do not have permission to set this status')]];
        }

        $updated = $this->repository->updateStatus($tenantId, $id, $newStatus, $userId);
        if (!$updated) {
            return ['ok' => false, 'errors' => ['general' => t('Failed to update status')]];
        }

        return ['ok' => true, 'errors' => []];
    }

    /**
     * Create a revision snapshot after a combined save (fields + optional status change).
     * Call this from the action after both updateFields and changeStatus have succeeded.
     *
     * @param array<string, mixed> $fieldValues  The final field values
     */
    public function createRevisionAfterSave(
        int $tenantId,
        int $handoverId,
        array $fieldValues,
        string $status,
        bool $fieldsChanged,
        bool $statusChanged,
        int $schemaVersion,
        int $userId,
    ): void {
        if ($this->revisionService === null) {
            return;
        }

        $changeType = HandoverRevisionService::CHANGE_TYPE_FIELDS;
        if ($fieldsChanged && $statusChanged) {
            $changeType = HandoverRevisionService::CHANGE_TYPE_BOTH;
        } elseif ($statusChanged) {
            $changeType = HandoverRevisionService::CHANGE_TYPE_STATUS;
        }

        $this->revisionService->createRevision(
            $tenantId,
            $handoverId,
            $fieldValues,
            $status,
            $schemaVersion,
            $userId,
            $changeType
        );
    }

    /**
     * Assign a handover to a user and optionally set a due date.
     * Sends notification email to the assignee.
     *
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function assign(
        int $tenantId,
        int $id,
        int $assignedTo,
        int $assignedBy,
        ?string $dueDate,
        string $permissionLevel,
    ): array {
        if ($permissionLevel !== self::PERMISSION_MANAGE) {
            return ['ok' => false, 'errors' => ['general' => t('Only managers can assign handovers')]];
        }

        $handover = $this->repository->findById($tenantId, $id);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        if ($assignedTo <= 0) {
            return ['ok' => false, 'errors' => ['assigned_to' => t('Please select an employee')]];
        }

        $updated = $this->repository->assign($tenantId, $id, $assignedTo, $assignedBy, $dueDate, $assignedBy);
        if (!$updated) {
            return ['ok' => false, 'errors' => ['general' => t('Failed to assign handover')]];
        }

        // Transition to in_progress if still draft
        if ($handover['status'] === self::STATUS_DRAFT) {
            $this->repository->updateStatus($tenantId, $id, self::STATUS_IN_PROGRESS, $assignedBy);
        }

        $updatedHandover = $this->repository->findById($tenantId, $id);
        $this->notificationService?->notifyAssigned($updatedHandover ?? $handover);

        return ['ok' => true, 'errors' => []];
    }

    /**
     * Submit handover for review (assignee action).
     * Changes status to under_review and notifies managers.
     *
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function submitForReview(
        int $tenantId,
        int $id,
        int $userId,
        string $permissionLevel,
    ): array {
        $handover = $this->repository->findById($tenantId, $id);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        // Only the assignee or a manager may submit for review
        $assignedTo = (int) ($handover['assigned_to'] ?? 0);
        $isAssignee = $assignedTo === $userId;
        $isManager = $permissionLevel === self::PERMISSION_MANAGE;

        if (!$isAssignee && !$isManager) {
            return ['ok' => false, 'errors' => ['general' => t('You are not assigned to this handover')]];
        }

        $currentStatus = (string) ($handover['status'] ?? '');
        if (!in_array($currentStatus, [self::STATUS_DRAFT, self::STATUS_IN_PROGRESS], true)) {
            return ['ok' => false, 'errors' => ['status' => t('Handover cannot be submitted for review in its current status')]];
        }

        $updated = $this->repository->updateStatus($tenantId, $id, self::STATUS_UNDER_REVIEW, $userId);
        if (!$updated) {
            return ['ok' => false, 'errors' => ['general' => t('Failed to update status')]];
        }

        $this->revisionService?->createRevision(
            $tenantId, $id, [], self::STATUS_UNDER_REVIEW, 1, $userId, HandoverRevisionService::CHANGE_TYPE_STATUS
        );

        $updatedHandover = $this->repository->findById($tenantId, $id);
        $this->notificationService?->notifyReviewRequested($updatedHandover ?? $handover);

        return ['ok' => true, 'errors' => []];
    }

    /**
     * Resend assignment notification email.
     *
     * @return array{ok: bool, errors: array<string, string>}
     */
    public function resendNotification(int $tenantId, int $id, string $permissionLevel): array
    {
        if ($permissionLevel !== self::PERMISSION_MANAGE) {
            return ['ok' => false, 'errors' => ['general' => t('Only managers can resend notifications')]];
        }

        $handover = $this->repository->findById($tenantId, $id);
        if ($handover === null) {
            return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
        }

        if (empty($handover['assigned_to'])) {
            return ['ok' => false, 'errors' => ['general' => t('Handover has no assignee')]];
        }

        $this->notificationService?->notifyAssigned($handover);

        return ['ok' => true, 'errors' => []];
    }

    /**
     * @return array{total: int, rows: list<array<string, mixed>>}
     */
    public function listPaged(int $tenantId, array $filters): array
    {
        return $this->repository->listPaged($tenantId, $filters);
    }

    public function findById(int $tenantId, int $id): ?array
    {
        return $this->repository->findById($tenantId, $id);
    }

    /**
     * @param list<int> $ids
     * @return array{ok: bool, count: int, error?: string}
     */
    public function deleteByIds(int $tenantId, array $ids, string $permissionLevel): array
    {
        if ($permissionLevel !== self::PERMISSION_MANAGE) {
            return ['ok' => false, 'count' => 0, 'error' => t('Only managers can delete handovers')];
        }

        if ($ids === []) {
            return ['ok' => false, 'count' => 0, 'error' => t('No handovers selected')];
        }

        $count = $this->repository->deleteByIds($tenantId, $ids);

        return ['ok' => true, 'count' => $count];
    }

    /**
     * Check if a user with the given permission level can edit a handover in the given status.
     */
    public function canEdit(string $status, string $permissionLevel): bool
    {
        if ($permissionLevel === self::PERMISSION_MANAGE) {
            return true;
        }

        if ($permissionLevel === self::PERMISSION_CREATE) {
            return in_array($status, self::EDITABLE_STATUSES_CREATE, true);
        }

        return false;
    }

    /**
     * Get the list of statuses a user with the given permission level can set.
     *
     * @return list<string>
     */
    public function getAllowedStatuses(string $permissionLevel): array
    {
        return self::ALLOWED_STATUS_BY_PERMISSION[$permissionLevel] ?? [];
    }

    /**
     * Validate field values against the schema snapshot.
     *
     * @param array<string, mixed> $fieldValues
     * @param list<array<string, mixed>> $schemaFields
     * @return array<string, string>
     */
    private function validateFieldValues(array $fieldValues, array $schemaFields): array
    {
        $errors = [];
        $allowedKeys = [];
        $requiredKeys = [];

        foreach ($schemaFields as $field) {
            $key = $field['key'] ?? null;
            if ($key === null) {
                continue;
            }
            $allowedKeys[$key] = true;
            if (!empty($field['required'])) {
                $requiredKeys[$key] = $field['label'] ?? $key;
            }
        }

        foreach ($fieldValues as $key => $value) {
            if (!isset($allowedKeys[$key])) {
                $errors[$key] = t('Unknown field: %s', $key);
            }
        }

        foreach ($requiredKeys as $key => $label) {
            $value = $fieldValues[$key] ?? '';
            if (is_string($value) && trim($value) === '') {
                $errors[$key] = t('%s is required', $label);
            }
        }

        return $errors;
    }

    /**
     * Get the human-readable label for a status.
     */
    public static function statusLabel(string $status): string
    {
        return match ($status) {
            self::STATUS_DRAFT => t('Draft'),
            self::STATUS_IN_PROGRESS => t('In progress'),
            self::STATUS_UNDER_REVIEW => t('Under review'),
            self::STATUS_COMPLETED => t('Completed'),
            self::STATUS_ARCHIVED => t('Archived'),
            default => $status,
        };
    }

    /**
     * Get the badge variant for a status.
     */
    public static function statusVariant(string $status): string
    {
        return match ($status) {
            self::STATUS_DRAFT => 'neutral',
            self::STATUS_IN_PROGRESS => 'warning',
            self::STATUS_UNDER_REVIEW => 'info',
            self::STATUS_COMPLETED => 'success',
            self::STATUS_ARCHIVED => 'neutral',
            default => 'neutral',
        };
    }
}
