<?php

namespace App\Console\Commands;

use App\Models\Dealer;
use App\Models\Department;
use App\Models\User;
use App\Services\DealerProvisioningService;
use App\Services\UserDepartmentResolver;
use Illuminate\Console\Command;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class VerifyDealerBaseline extends Command
{
    protected $signature = 'dealers:verify-baseline
                            {--fix : Attempt to repair drift using DealerProvisioningService}
                            {--dealer_id= : Limit verification to a single dealer id}
                            {--json : Output JSON instead of table}';

    protected $description = 'Verify (and optionally repair) dealer baseline provisioning invariants.';

    public function handle(DealerProvisioningService $provisioningService, UserDepartmentResolver $userDepartmentResolver): int
    {
        $dealerIdOption = $this->option('dealer_id');
        $fix = (bool) $this->option('fix');
        $asJson = (bool) $this->option('json');

        $dealersQuery = Dealer::query();
        if ($dealerIdOption !== null && $dealerIdOption !== '') {
            $dealersQuery->whereKey((int) $dealerIdOption);
        }

        $dealers = $dealersQuery->get();
        if ($dealers->isEmpty()) {
            $this->error('No dealers found for verification.');

            return 1;
        }

        $allOk = true;
        $rows = [];
        $jsonOutput = [];

        $baselineDepartments = config('mtt_tenant_baseline.departments', []);
        $currentBaselineVersion = (string) config('mtt_tenant_baseline.baseline_version', '');
        $allRoles = config('mtt_roles', []);
        $excludedRoles = config('mtt_tenant_baseline.excluded_roles_for_dealers', []);
        $expectedRoles = array_values(array_diff($allRoles, $excludedRoles));
        $permissions = config('mtt_permissions', []);
        $matrix = config('mtt_rbac_matrix', []);
        $builtinUsers = config('mtt_tenant_baseline.builtin_users', []);

        foreach ($dealers as $dealer) {
            $missingDepts = [];
            $missingRoles = [];
            $missingPerms = [];
            $userIssues = [];
            $notes = [];
            $status = 'ok';

            // Baseline version drift check.
            if ($currentBaselineVersion !== '' && $dealer->baseline_version_applied !== null && $dealer->baseline_version_applied !== $currentBaselineVersion) {
                $notes[] = sprintf(
                    'Baseline version drift: dealer=%s, config=%s',
                    $dealer->baseline_version_applied,
                    $currentBaselineVersion
                );
                $status = 'drift';
                $allOk = false;
            }

            // 1) Baseline departments
            foreach ($baselineDepartments as $name) {
                $dept = Department::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('name', $name)
                    ->first();

                if (! $dept) {
                    $missingDepts[] = $name.' (missing)';
                } elseif (! $dept->is_builtin) {
                    $missingDepts[] = $name.' (not builtin)';
                }
            }

            // 2) Dealer roles (excluding platform-only roles)
            foreach ($expectedRoles as $roleName) {
                $role = Role::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('name', $roleName)
                    ->first();

                if (! $role) {
                    $missingRoles[] = $roleName;
                }
            }

            // 3) Permissions (global; checked once per dealer for consistency)
            foreach ($permissions as $permissionName) {
                $permission = Permission::query()
                    ->where('name', $permissionName)
                    ->where('guard_name', 'web')
                    ->first();

                if (! $permission) {
                    $missingPerms[] = $permissionName;
                }
            }

            // 4) Role-permission mappings per dealer
            foreach ($expectedRoles as $roleName) {
                $expectedPerms = $matrix[$roleName] ?? [];
                sort($expectedPerms);

                $role = Role::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('name', $roleName)
                    ->first();

                if (! $role) {
                    // Already counted as missing role above.
                    continue;
                }

                $actualPerms = $role->permissions->pluck('name')->all();
                sort($actualPerms);

                if ($expectedPerms !== $actualPerms) {
                    $missingRoles[] = $roleName.' (permission mismatch)';
                }
            }

            // 5) Built-in users per dealer
            foreach ($builtinUsers as $template) {
                $templateEmail = $template['email'] ?? null;
                $roleName = $template['role'] ?? null;

                if (! $templateEmail || ! $roleName) {
                    continue;
                }

                [$local] = explode('@', $templateEmail, 2);
                if (! $local) {
                    continue;
                }

                if (empty($dealer->domain)) {
                    $userIssues[] = "Dealer has no domain; cannot materialize builtin user {$templateEmail}.";
                    continue;
                }

                $expectedEmail = $local.'@'.$dealer->domain;

                /** @var User|null $user */
                $user = User::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('email', $expectedEmail)
                    ->first();

                if (! $user) {
                    $userIssues[] = "Missing builtin user {$expectedEmail} (role {$roleName}).";
                    continue;
                }

                if (! $user->is_builtin) {
                    $userIssues[] = "User {$expectedEmail} is not marked as builtin.";
                }

                $roleNames = $user->roles->pluck('name')->values()->all();
                if ($roleNames !== [$roleName]) {
                    $userIssues[] = "User {$expectedEmail} has roles [".implode(', ', $roleNames)."], expected [{$roleName}].";
                }

                $resolvedDeptId = $userDepartmentResolver->resolveDepartmentIdForUser($user);
                if ($resolvedDeptId && $user->department_id !== $resolvedDeptId) {
                    $userIssues[] = "User {$expectedEmail} department_id {$user->department_id} does not match resolver {$resolvedDeptId}.";
                }
            }

            $hasDrift = $missingDepts !== [] || $missingRoles !== [] || $missingPerms !== [] || $userIssues !== [];

            if (($hasDrift || ($currentBaselineVersion !== '' && $dealer->baseline_version_applied !== $currentBaselineVersion)) && $fix) {
                $notes[] = 'Attempting auto-fix via DealerProvisioningService.';

                $provisioningService->provision($dealer, [
                    'include_passwords' => false,
                    'run_type' => 'verify_fix',
                ]);

                // Re-run checks after fix.
                $this->info("Re-running checks for dealer {$dealer->id} after fix...");

                // Recursive-style re-check (without infinite loop).
                $rechecked = $this->recheckDealer($dealer, $userDepartmentResolver, $baselineDepartments, $expectedRoles, $permissions, $matrix, $builtinUsers, $currentBaselineVersion);
                $missingDepts = $rechecked['missingDepts'];
                $missingRoles = $rechecked['missingRoles'];
                $missingPerms = $rechecked['missingPerms'];
                $userIssues = $rechecked['userIssues'];
                $versionDrift = $rechecked['versionDrift'];

                if ($missingDepts === [] && $missingRoles === [] && $missingPerms === [] && $userIssues === [] && ! $versionDrift) {
                    $status = 'fixed';
                } else {
                    $status = 'drift';
                    $allOk = false;
                    $notes[] = 'Auto-fix attempted but drift remains.';
                }
            } elseif ($hasDrift) {
                $status = 'drift';
                $allOk = false;
            }

            $rows[] = [
                $dealer->id,
                $dealer->code ?? '',
                $dealer->name,
                $status,
                implode('; ', $missingDepts) ?: '-',
                implode('; ', $missingRoles) ?: '-',
                implode('; ', $missingPerms) ?: '-',
                implode('; ', $userIssues) ?: '-',
                implode('; ', $notes) ?: '-',
            ];

            $jsonOutput[] = [
                'dealer_id' => $dealer->id,
                'code' => $dealer->code,
                'name' => $dealer->name,
                'status' => $status,
                'missing_departments' => $missingDepts,
                'missing_roles' => $missingRoles,
                'missing_permissions' => $missingPerms,
                'user_issues' => $userIssues,
                'notes' => $notes,
                'baseline_version_applied' => $dealer->baseline_version_applied,
                'baseline_version_config' => $currentBaselineVersion,
            ];
        }

        if ($asJson) {
            $this->line(json_encode($jsonOutput, JSON_PRETTY_PRINT));
        } else {
            $this->table(
                ['Dealer ID', 'Code', 'Name', 'Status', 'Missing Depts', 'Missing Roles', 'Missing Perms', 'User Issues', 'Notes'],
                $rows
            );
        }

        return $allOk ? 0 : 1;
    }

    /**
     * Re-run checks for a single dealer after an attempted fix.
     *
     * @return array{missingDepts:array,missingRoles:array,missingPerms:array,userIssues:array,versionDrift:bool}
     */
    protected function recheckDealer(
        Dealer $dealer,
        UserDepartmentResolver $userDepartmentResolver,
        array $baselineDepartments,
        array $expectedRoles,
        array $permissions,
        array $matrix,
        array $builtinUsers,
        string $currentBaselineVersion
    ): array {
        $missingDepts = [];
        $missingRoles = [];
        $missingPerms = [];
        $userIssues = [];
        $versionDrift = false;

        if ($currentBaselineVersion !== '' && $dealer->baseline_version_applied !== $currentBaselineVersion) {
            $versionDrift = true;
        }

        foreach ($baselineDepartments as $name) {
            $dept = Department::query()
                ->where('dealer_id', $dealer->id)
                ->where('name', $name)
                ->first();

            if (! $dept) {
                $missingDepts[] = $name.' (missing)';
            } elseif (! $dept->is_builtin) {
                $missingDepts[] = $name.' (not builtin)';
            }
        }

        foreach ($expectedRoles as $roleName) {
            $role = Role::query()
                ->where('dealer_id', $dealer->id)
                ->where('name', $roleName)
                ->first();

            if (! $role) {
                $missingRoles[] = $roleName;
                continue;
            }

            $expectedPerms = $matrix[$roleName] ?? [];
            sort($expectedPerms);

            $actualPerms = $role->permissions->pluck('name')->all();
            sort($actualPerms);

            if ($expectedPerms !== $actualPerms) {
                $missingRoles[] = $roleName.' (permission mismatch)';
            }
        }

        foreach ($permissions as $permissionName) {
            $permission = Permission::query()
                ->where('name', $permissionName)
                ->where('guard_name', 'web')
                ->first();

            if (! $permission) {
                $missingPerms[] = $permissionName;
            }
        }

        foreach ($builtinUsers as $template) {
            $templateEmail = $template['email'] ?? null;
            $roleName = $template['role'] ?? null;

            if (! $templateEmail || ! $roleName) {
                continue;
            }

            [$local] = explode('@', $templateEmail, 2);
            if (! $local) {
                continue;
            }

            if (empty($dealer->domain)) {
                $userIssues[] = "Dealer has no domain; cannot materialize builtin user {$templateEmail}.";
                continue;
            }

            $expectedEmail = $local.'@'.$dealer->domain;

            /** @var User|null $user */
            $user = User::query()
                ->where('dealer_id', $dealer->id)
                ->where('email', $expectedEmail)
                ->first();

            if (! $user) {
                $userIssues[] = "Missing builtin user {$expectedEmail} (role {$roleName}).";
                continue;
            }

            if (! $user->is_builtin) {
                $userIssues[] = "User {$expectedEmail} is not marked as builtin.";
            }

            $roleNames = $user->roles->pluck('name')->values()->all();
            if ($roleNames !== [$roleName]) {
                $userIssues[] = "User {$expectedEmail} has roles [".implode(', ', $roleNames)."], expected [{$roleName}].";
            }

            $resolvedDeptId = $userDepartmentResolver->resolveDepartmentIdForUser($user);
            if ($resolvedDeptId && $user->department_id !== $resolvedDeptId) {
                $userIssues[] = "User {$expectedEmail} department_id {$user->department_id} does not match resolver {$resolvedDeptId}.";
            }
        }

        return compact('missingDepts', 'missingRoles', 'missingPerms', 'userIssues', 'versionDrift');
    }
}

