<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\BaselineProfile;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use InvalidArgumentException;
use Illuminate\Console\Command;

class BackfillBaselineScopeV2 extends Command
{
    protected $signature = 'tenantpilot:baseline-scope-v2:backfill
        {--workspace= : Restrict to a workspace id}
        {--chunk=100 : Chunk size for large scans}
        {--write : Persist canonical V2 scope rows}
        {--confirm-write : Required acknowledgement before mutating rows}';

    protected $description = 'Preview or commit canonical Baseline Scope V2 backfill for legacy baseline profile rows.';

    public function handle(WorkspaceAuditLogger $auditLogger): int
    {
        $write = (bool) $this->option('write');

        if ($write && ! (bool) $this->option('confirm-write')) {
            $this->error('Explicit write confirmation required. Re-run with --write --confirm-write.');

            return self::FAILURE;
        }

        $workspaceOption = $this->option('workspace');

        if ($workspaceOption !== null && ! is_numeric($workspaceOption)) {
            $this->error('The --workspace option must be a numeric workspace id.');

            return self::FAILURE;
        }

        $workspaceId = is_numeric($workspaceOption) ? (int) $workspaceOption : null;
        $scan = $this->scanCandidates(
            chunkSize: max(1, (int) $this->option('chunk')),
            workspaceId: $workspaceId,
        );

        $mode = $write ? 'commit' : 'preview';
        $candidateCount = count($scan['candidates']);
        $invalidCount = count($scan['invalid']);

        $this->info(sprintf('Mode: %s', $mode));
        $this->info('Scope surface: baseline_profiles_only');
        $this->info(sprintf('Candidate count: %d', $candidateCount));

        foreach ($scan['candidates'] as $candidate) {
            $this->line(sprintf(' - %s', $candidate['summary']));
        }

        if ($invalidCount > 0) {
            $this->warn(sprintf('Invalid rows: %d', $invalidCount));

            foreach ($scan['invalid'] as $invalidRow) {
                $this->warn(sprintf(' - %s', $invalidRow));
            }
        }

        if ($candidateCount === 0) {
            $this->info('No baseline profile scope rows require backfill.');
            $this->info('Rewritten count: 0');
            $this->info('Audit logged: no');

            return self::SUCCESS;
        }

        if ($write && $invalidCount > 0) {
            $this->error('Backfill aborted because invalid scope rows were detected. Resolve them before committing.');

            return self::FAILURE;
        }

        if (! $write) {
            $this->info('Rewritten count: 0');
            $this->info('Audit logged: no');

            return self::SUCCESS;
        }

        $rewrittenCount = 0;
        $auditLogged = false;

        foreach ($scan['candidates'] as $candidate) {
            $profile = BaselineProfile::query()
                ->with('workspace')
                ->find($candidate['id']);

            if (! $profile instanceof BaselineProfile) {
                continue;
            }

            $before = $profile->rawScopeJsonb();
            $after = $profile->canonicalScopeJsonb();

            if (! $profile->rewriteScopeToCanonicalV2()) {
                continue;
            }

            $workspace = $profile->workspace;

            if ($workspace instanceof Workspace) {
                $auditLogger->log(
                    workspace: $workspace,
                    action: AuditActionId::BaselineProfileScopeBackfilled,
                    context: [
                        'metadata' => [
                            'source' => 'tenantpilot:baseline-scope-v2:backfill',
                            'workspace_id' => (int) $profile->workspace_id,
                            'baseline_profile_id' => (int) $profile->getKey(),
                            'mode' => 'commit',
                            'before_scope' => $before,
                            'after_scope' => $after,
                        ],
                    ],
                    resourceType: 'baseline_profile',
                    resourceId: (string) $profile->getKey(),
                    targetLabel: (string) $profile->name,
                    summary: sprintf('Baseline profile "%s" scope backfilled to canonical V2.', (string) $profile->name),
                );

                $auditLogged = true;
            }

            $rewrittenCount++;
        }

        $this->info(sprintf('Rewritten count: %d', $rewrittenCount));
        $this->info(sprintf('Audit logged: %s', $auditLogged ? 'yes' : 'no'));

        return self::SUCCESS;
    }

    /**
     * @return array{candidates: list<array{id: int, summary: string}>, invalid: list<string>}
     */
    private function scanCandidates(int $chunkSize, ?int $workspaceId = null): array
    {
        $candidates = [];
        $invalid = [];

        BaselineProfile::query()
            ->when(
                $workspaceId !== null,
                fn ($query) => $query->where('workspace_id', $workspaceId),
            )
            ->orderBy('id')
            ->chunkById($chunkSize, function ($profiles) use (&$candidates, &$invalid): void {
                foreach ($profiles as $profile) {
                    if (! $profile instanceof BaselineProfile) {
                        continue;
                    }

                    try {
                        if (! $profile->requiresScopeSaveForward()) {
                            continue;
                        }

                        $candidates[] = [
                            'id' => (int) $profile->getKey(),
                            'summary' => $this->candidateSummary($profile),
                        ];
                    } catch (InvalidArgumentException $exception) {
                        $invalid[] = sprintf(
                            '#%d "%s": %s',
                            (int) $profile->getKey(),
                            (string) $profile->name,
                            $exception->getMessage(),
                        );
                    }
                }
            });

        return [
            'candidates' => $candidates,
            'invalid' => $invalid,
        ];
    }

    private function candidateSummary(BaselineProfile $profile): string
    {
        $groupSummary = collect($profile->normalizedScope()->summaryGroups())
            ->map(function (array $group): string {
                return $group['group_label'].': '.implode(', ', $group['selected_subject_types']);
            })
            ->implode('; ');

        return sprintf(
            '#%d workspace=%d "%s" => %s',
            (int) $profile->getKey(),
            (int) $profile->workspace_id,
            (string) $profile->name,
            $groupSummary,
        );
    }
}