<?php

namespace MintyPHP\Module\Security\Service;

/**
 * The fixed internal security-check process.
 *
 * Identical for every check, so it lives in code (not the DB). Step labels and
 * descriptions are translation keys — call t() at render time. The tech-checklist
 * step (perform_check) expands into the per-product items from the check's
 * tech_schema_snapshot.
 *
 * Pure logic only (no DB, no HTTP) — fully unit-testable.
 *
 * @api Consumed by Security pages, views and SecurityCheckService.
 */
final class SecurityCheckProcess
{
    public const STATUS_OPEN = 'open';
    public const STATUS_IN_PROGRESS = 'in_progress';
    public const STATUS_COMPLETED = 'completed';
    public const STATUS_ARCHIVED = 'archived';

    public const STEP_INFORMATION = 'information_gathering';
    public const STEP_PERFORM_CHECK = 'perform_check';
    public const STEP_REPORT = 'report';

    public const KIND_STEP = 'step';
    public const KIND_INFO = 'info';
    public const KIND_TECH_CHECKLIST = 'tech_checklist';

    public const FIELD_TEXTAREA = 'textarea';
    public const FIELD_DATETIME = 'datetime';

    /** Field key of the official, customer-facing report written on the final step. */
    public const FIELD_CUSTOMER_REPORT = 'customer_report';

    /**
     * Ordered fixed process steps.
     *
     * @return list<array{key: string, label: string, description: string, kind: string, fields?: list<array{key: string, label: string, type: string, required: bool, rows?: int}>}>
     */
    public function steps(): array
    {
        return [
            [
                'key' => 'beauftragung',
                'label' => 'Order received',
                'description' => 'The security check was sold for one or more customer projects; sales briefs the security officer with all details.',
                'kind' => self::KIND_STEP,
            ],
            [
                'key' => self::STEP_INFORMATION,
                'label' => 'Gather information',
                'description' => 'Collect all relevant information from Customer Care / the project lead.',
                'kind' => self::KIND_INFO,
                'fields' => [
                    ['key' => 'technical_contact', 'label' => 'Technical contact at customer', 'type' => self::FIELD_TEXTAREA, 'required' => true],
                    ['key' => 'deployment', 'label' => 'Deployment location & type', 'type' => self::FIELD_TEXTAREA, 'required' => true],
                    ['key' => 'system_info', 'label' => 'Technical system information', 'type' => self::FIELD_TEXTAREA, 'required' => true],
                    ['key' => 'external_systems', 'label' => 'External systems / API endpoints (exact URLs)', 'type' => self::FIELD_TEXTAREA, 'required' => true],
                    ['key' => 'special_notes', 'label' => 'Special notes', 'type' => self::FIELD_TEXTAREA, 'required' => false],
                ],
            ],
            [
                'key' => 'scheduling',
                'label' => 'Scheduling',
                'description' => 'Agree internally and with the customer on the check date. Only the 2–3h go-live window is relevant to the customer.',
                'kind' => self::KIND_STEP,
                'fields' => [
                    ['key' => 'golive_start', 'label' => 'Go-live window start', 'type' => self::FIELD_DATETIME, 'required' => true],
                    ['key' => 'golive_end', 'label' => 'Go-live window end', 'type' => self::FIELD_DATETIME, 'required' => true],
                ],
            ],
            [
                'key' => 'blocker_appointment',
                'label' => 'Internal blocker appointment',
                'description' => 'Create a 1–2 day internal blocker; invite Customer Care, Customizing and Sales.',
                'kind' => self::KIND_STEP,
                'fields' => [
                    ['key' => 'blocker_start', 'label' => 'Blocker start', 'type' => self::FIELD_DATETIME, 'required' => true],
                    ['key' => 'blocker_end', 'label' => 'Blocker end', 'type' => self::FIELD_DATETIME, 'required' => true],
                ],
            ],
            [
                'key' => self::STEP_PERFORM_CHECK,
                'label' => 'Perform security check',
                'description' => 'Work through the product-specific technical checklist.',
                'kind' => self::KIND_TECH_CHECKLIST,
            ],
            [
                'key' => self::STEP_REPORT,
                'label' => 'Create report & notify customer',
                'description' => 'Create the report for the customer and notify them.',
                'kind' => self::KIND_STEP,
                'fields' => [
                    ['key' => self::FIELD_CUSTOMER_REPORT, 'label' => 'Official customer report', 'type' => self::FIELD_TEXTAREA, 'required' => true, 'rows' => 12],
                ],
            ],
        ];
    }

    /**
     * Keys of steps completed manually (everything except the tech-checklist step).
     *
     * @return list<string>
     */
    public function manualStepKeys(): array
    {
        $keys = [];
        foreach ($this->steps() as $step) {
            if ($step['kind'] !== self::KIND_TECH_CHECKLIST) {
                $keys[] = $step['key'];
            }
        }

        return $keys;
    }

    /**
     * Structured fields captured on a given step (info textareas, datetimes, …).
     *
     * @return list<array{key: string, label: string, type: string, required: bool, rows?: int}>
     */
    public function stepFields(string $stepKey): array
    {
        foreach ($this->steps() as $step) {
            if ($step['key'] === $stepKey) {
                return $step['fields'] ?? [];
            }
        }

        return [];
    }

    /**
     * Keys of a step's fields that must be filled before it can be completed.
     * (E.g. "Special notes" and the free-text note stay optional.)
     *
     * @return list<string>
     */
    public function requiredFieldKeys(string $stepKey): array
    {
        $keys = [];
        foreach ($this->stepFields($stepKey) as $field) {
            if ($field['required']) {
                $keys[] = $field['key'];
            }
        }

        return $keys;
    }

    /**
     * Extract the checkable items from a tech-schema snapshot.
     *
     * @param array<string, mixed> $techSnapshot
     * @return list<array{key: string, label: string}>
     */
    public static function techCheckItems(array $techSnapshot): array
    {
        $items = is_array($techSnapshot['items'] ?? null) ? $techSnapshot['items'] : [];
        $checks = [];
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            if (($item['type'] ?? '') === 'check' && trim((string) ($item['key'] ?? '')) !== '') {
                $checks[] = ['key' => (string) $item['key'], 'label' => (string) ($item['label'] ?? '')];
            }
        }

        return $checks;
    }

    /**
     * Compute progress + derived status purely from state.
     *
     * @param array<string, mixed> $processState  per-step state keyed by step key
     * @param array<string, mixed> $techSnapshot  frozen tech-schema snapshot
     * @param array<string, mixed> $techState     per-item state keyed by item key
     * @return array{done: int, total: int, percent: int, manual_done: int, manual_total: int, tech_done: int, tech_total: int, step6_done: bool, status: string}
     */
    public function computeProgress(array $processState, array $techSnapshot, array $techState, bool $archived = false): array
    {
        $manualKeys = $this->manualStepKeys();
        $manualTotal = count($manualKeys);
        $manualDone = 0;
        foreach ($manualKeys as $key) {
            if (!empty($processState[$key]['done'])) {
                $manualDone++;
            }
        }

        $techItems = self::techCheckItems($techSnapshot);
        $techTotal = count($techItems);
        $techDone = 0;
        foreach ($techItems as $item) {
            if (!empty($techState[$item['key']]['done'])) {
                $techDone++;
            }
        }

        $step6Done = $techTotal === 0 ? true : ($techDone === $techTotal);

        $total = $manualTotal + $techTotal;
        $done = $manualDone + $techDone;
        $percent = $total > 0 ? (int) floor(($done / $total) * 100) : 0;

        if ($archived) {
            $status = self::STATUS_ARCHIVED;
        } elseif ($total > 0 && $done >= $total) {
            $status = self::STATUS_COMPLETED;
        } elseif ($done > 0) {
            $status = self::STATUS_IN_PROGRESS;
        } else {
            $status = self::STATUS_OPEN;
        }

        return [
            'done' => $done,
            'total' => $total,
            'percent' => $percent,
            'manual_done' => $manualDone,
            'manual_total' => $manualTotal,
            'tech_done' => $techDone,
            'tech_total' => $techTotal,
            'step6_done' => $step6Done,
            'status' => $status,
        ];
    }

    /**
     * Whether every step preceding the final "report" step is complete: all
     * manual steps except the report itself are done AND the technical checklist
     * is fully ticked. Gates the "Generate PDF customer report" action so the
     * report can only be produced once the check has actually been carried out.
     *
     * @param array<string, mixed> $processState
     * @param array<string, mixed> $techSnapshot
     * @param array<string, mixed> $techState
     */
    public function reportPrerequisitesMet(array $processState, array $techSnapshot, array $techState): bool
    {
        foreach ($this->manualStepKeys() as $key) {
            if ($key === self::STEP_REPORT) {
                continue;
            }
            if (empty($processState[$key]['done'])) {
                return false;
            }
        }

        return $this->computeProgress($processState, $techSnapshot, $techState)['step6_done'];
    }

    public static function statusLabel(string $status): string
    {
        return match ($status) {
            self::STATUS_OPEN => 'Open',
            self::STATUS_IN_PROGRESS => 'In progress',
            self::STATUS_COMPLETED => 'Completed',
            self::STATUS_ARCHIVED => 'Archived',
            default => $status,
        };
    }

    public static function statusVariant(string $status): string
    {
        return match ($status) {
            self::STATUS_IN_PROGRESS => 'warning',
            self::STATUS_COMPLETED => 'success',
            default => 'neutral',
        };
    }
}
