<?php

namespace App\Services;

use App\Models\Dealer;
use App\Models\Department;
use App\Models\DealerProvisioningRun;
use App\Models\User;
use App\Services\UserDepartmentResolver;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
use Throwable;

class DealerProvisioningService
{
    /**
     * Provision baseline departments, roles and permissions for a dealer.
     *
     * Idempotent: safe to call multiple times.
     */
    public function provision(Dealer $dealer, array $opts = []): array
    {
        $includePasswords = (bool) ($opts['include_passwords'] ?? false);
        $runType = (string) ($opts['run_type'] ?? 'create');
        $triggeredByUserId = $opts['triggered_by_user_id'] ?? null;

        $run = DealerProvisioningRun::create([
            'dealer_id' => $dealer->id,
            'triggered_by_user_id' => $triggeredByUserId,
            'run_type' => $runType,
            'status' => 'running',
            'started_at' => now(),
        ]);

        $result = [
            'counts' => [
                'departments_created' => 0,
                'roles_created' => 0,
                'permissions_created' => 0,
                'users_created' => 0,
                'users_existing' => 0,
            ],
            'users' => [
                'created' => [],
                'existing' => [],
            ],
            'run_type' => $runType,
        ];

        try {
            DB::transaction(function () use ($dealer, $includePasswords, $runType, &$result): void {
                $departmentsCreated = $this->provisionDepartments($dealer);
                [$rolesCreated, $permissionsCreated] = $this->provisionRbac($dealer);
                $usersSummary = $this->provisionBuiltinUsers($dealer, $includePasswords);

                $result['counts']['departments_created'] = $departmentsCreated;
                $result['counts']['roles_created'] = $rolesCreated;
                $result['counts']['permissions_created'] = $permissionsCreated;
                $result['counts']['users_created'] = count($usersSummary['created'] ?? []);
                $result['counts']['users_existing'] = count($usersSummary['existing'] ?? []);

                $result['users'] = $usersSummary;

                // Safety check: if we created built-in users but none were assigned a department, surface an error early.
                $builtinCount = User::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('is_builtin', true)
                    ->count();

                $assignedCount = User::query()
                    ->where('dealer_id', $dealer->id)
                    ->where('is_builtin', true)
                    ->whereNotNull('department_id')
                    ->count();

                if ($builtinCount > 0 && $assignedCount === 0) {
                    throw new \RuntimeException("Provisioning created built-in users for dealer {$dealer->id} (run_type={$runType}) but none were assigned departments. Check role-to-department mapping and team context.");
                }
            });

            $dealer->forceFill([
                'provisioned_at' => now(),
                'provisioning_status' => 'provisioned',
                'provisioning_error' => null,
                'baseline_version_applied' => config('mtt_tenant_baseline.baseline_version'),
                'baseline_last_applied_at' => now(),
            ])->save();

            $sanitizedUsers = [
                'created' => array_map(
                    fn (array $user) => [
                        'name' => $user['name'],
                        'email' => $user['email'],
                        'role' => $user['role'],
                    ],
                    $result['users']['created'] ?? []
                ),
                'existing' => $result['users']['existing'] ?? [],
            ];

            $run->forceFill([
                'status' => 'success',
                'finished_at' => now(),
                'summary_json' => [
                    'counts' => $result['counts'],
                    'users' => $sanitizedUsers,
                    'run_type' => $result['run_type'],
                ],
                'error_message' => null,
            ])->save();

            return $result;
        } catch (Throwable $e) {
            $this->markFailed($dealer, $run, $e);
            Log::error('Dealer provisioning failed', [
                'dealer_id' => $dealer->id,
                'run_id' => $run->id,
                'run_type' => $runType,
                'exception_class' => get_class($e),
                'message' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    public function markFailed(Dealer $dealer, DealerProvisioningRun $run, Throwable $e): void
    {
        $message = mb_substr($e->getMessage(), 0, 5000);

        $dealer->forceFill([
            'provisioning_status' => 'failed',
            'provisioning_error' => $message,
        ])->save();

        $run->forceFill([
            'status' => 'failed',
            'finished_at' => now(),
            'error_message' => $message,
        ])->save();
    }

    protected function provisionDepartments(Dealer $dealer): int
    {
        $baselineNames = config('mtt_tenant_baseline.departments', []);

        $createdCount = 0;

        foreach ($baselineNames as $name) {
            $code = Str::slug($name, '_');
            $slug = Str::slug($name);

            $department = Department::updateOrCreate(
                [
                    'dealer_id' => $dealer->id,
                    'name' => $name,
                ],
                [
                    'code' => $code,
                    'slug' => $slug,
                    'is_system' => true,
                    'is_builtin' => true,
                ]
            );

            if ($department->wasRecentlyCreated) {
                $createdCount++;
            }
        }

        return $createdCount;
    }

    /**
     * @return array{0:int,1:int} [rolesCreated, permissionsCreated]
     */
    protected function provisionRbac(Dealer $dealer): array
    {
        $guardName = 'web';
        $roles = config('mtt_roles', []);
        $permissions = config('mtt_permissions', []);
        $matrix = config('mtt_rbac_matrix', []);
        $excludedDealerRoles = config('mtt_tenant_baseline.excluded_roles_for_dealers', []);

        $permissionsCreated = 0;

        // Ensure all permissions exist globally.
        foreach ($permissions as $permissionName) {
            $permission = Permission::findOrCreate($permissionName, $guardName);
            if ($permission->wasRecentlyCreated) {
                $permissionsCreated++;
            }
        }

        /** @var PermissionRegistrar $registrar */
        $registrar = app(PermissionRegistrar::class);
        $registrar->setPermissionsTeamId($dealer->id);

        $rolesCreated = 0;

        foreach ($roles as $roleName) {
            if (in_array($roleName, $excludedDealerRoles, true)) {
                continue;
            }

            $role = Role::findOrCreate($roleName, $guardName, $dealer->id);
            if ($role->wasRecentlyCreated) {
                $rolesCreated++;
            }
            $permissionNames = $matrix[$roleName] ?? [];

            // Ensure any permissions used only in matrix also exist.
            foreach ($permissionNames as $permissionName) {
                $permission = Permission::findOrCreate($permissionName, $guardName);
                if ($permission->wasRecentlyCreated) {
                    $permissionsCreated++;
                }
            }

            $role->syncPermissions($permissionNames);
        }

        $registrar->setPermissionsTeamId(null);
        $registrar->forgetCachedPermissions();

        return [$rolesCreated, $permissionsCreated];
    }

    /**
     * Create or update built-in baseline users for this dealer.
     *
     * Returns a summary array with created and existing users.
     */
    protected function provisionBuiltinUsers(Dealer $dealer, bool $includePasswords): array
    {
        $templates = config('mtt_tenant_baseline.builtin_users', []);

        $created = [];
        $existing = [];

        if ($templates === []) {
            return ['created' => [], 'existing' => []];
        }

        if (empty($dealer->domain)) {
            return ['created' => [], 'existing' => []];
        }

        /** @var PermissionRegistrar $registrar */
        $registrar = app(PermissionRegistrar::class);
        $registrar->setPermissionsTeamId($dealer->id);

        /** @var UserDepartmentResolver $deptResolver */
        $deptResolver = app(UserDepartmentResolver::class);

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

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

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

            // Replace base domain with dealer domain.
            $targetEmail = $local.'@'.$dealer->domain;

            $user = User::query()
                ->where('dealer_id', $dealer->id)
                ->where('email', $targetEmail)
                ->first();

            if (!$user) {
                $tempPassword = Str::password(12);

                $user = new User();
                $user->dealer_id = $dealer->id;
                $user->email = $targetEmail;

                // Split "X Built-in" into name + surname if applicable.
                if (str_ends_with($displayName, ' Built-in')) {
                    $baseName = trim(substr($displayName, 0, -strlen(' Built-in')));
                    $user->name = $baseName;
                    $user->surname = 'Built-in';
                } else {
                    $user->name = $displayName;
                    $user->surname = '';
                }

                $user->password = Hash::make($tempPassword);
                $user->is_disabled = false;
                $user->force_password_change = true;
                $user->is_builtin = true;
                $user->save();

                $user->syncRoles([$roleName]);
                $deptResolver->enforceForDealer($user->fresh(), $dealer->id);

                $createdEntry = [
                    'name' => $user->full_name,
                    'email' => $user->email,
                    'role' => $roleName,
                ];

                if ($includePasswords) {
                    $createdEntry['temp_password'] = $tempPassword;
                }

                $created[] = $createdEntry;
            } else {
                $user->syncRoles([$roleName]);
                $deptResolver->enforceForDealer($user->fresh(), $dealer->id);

                $existing[] = [
                    'name' => $user->full_name,
                    'email' => $user->email,
                    'role' => $roleName,
                ];
            }
        }

        $registrar->setPermissionsTeamId(null);
        $registrar->forgetCachedPermissions();

        return [
            'created' => $created,
            'existing' => $existing,
        ];
    }
}

