<?php

namespace Database\Seeders\Simulation;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\CustomerAppointment;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoginAttempt;
use App\Models\NotificationTemplate;
use App\Models\Task;
use App\Models\User;
use App\Services\AuditService;
use App\Services\LoanReminderService;
use App\Services\LoginAttemptService;
use App\Services\ManagementAlertService;
use App\Services\ManagementWorkSettings;
use App\Services\TaskAutomationService;
use App\Support\Simulation\SimulationOperationsSurfacesExpectations;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class SimulationOperationsSurfacesSeeder extends Seeder
{
    public function run(
        ManagementWorkSettings $workSettings,
        TaskAutomationService $taskAutomation,
        LoanReminderService $reminders,
        ManagementAlertService $alerts,
        LoginAttemptService $loginAttempts,
    ): void {
        $asOf = CarbonImmutable::parse((string) config('simulation.as_of_date', SimulationOperationsSurfacesExpectations::AS_OF_DATE))
            ->setTime(12, 0);
        $previousTestNow = CarbonImmutable::getTestNow();
        CarbonImmutable::setTestNow($asOf);

        try {
            DB::transaction(function () use ($asOf, $workSettings, $taskAutomation, $reminders, $alerts, $loginAttempts): void {
                $this->assertFinancialControl('before');

                $business = Business::query()->firstOrFail();
                $dar = Branch::query()->where('code', 'SIM-DAR')->firstOrFail();
                $arusha = Branch::query()->where('code', 'SIM-ARU')->firstOrFail();
                $darManager = User::query()->where('username', 'sim.dar.manager')->firstOrFail();
                $darLoanOfficer = User::query()->where('username', 'sim.dar.loan')->firstOrFail();
                $arushaManager = User::query()->where('username', 'sim.arusha.manager')->firstOrFail();
                $arushaLoanOfficer = User::query()->where('username', 'sim.arusha.loan')->firstOrFail();
                $darCustomer = Customer::query()->where('customer_number', 'SIM-CUS-DAR-001')->firstOrFail();
                $arushaCustomer = Customer::query()->where('customer_number', 'SIM-CUS-ARU-001')->firstOrFail();
                $overdueLoan = Loan::query()->where('purpose', SimulationOperationsSurfacesExpectations::OVERDUE_LOAN_PURPOSE)->firstOrFail();

                $workSettings->store($business->id, [
                    'collection_tasks_enabled' => true,
                    'collection_task_overdue_days' => SimulationOperationsSurfacesExpectations::OVERDUE_DAYS_THRESHOLD,
                    'large_loan_threshold' => 0,
                    'large_expense_threshold' => 0,
                    'overdue_alert_days' => SimulationOperationsSurfacesExpectations::OVERDUE_DAYS_THRESHOLD,
                    'low_account_balance_threshold' => 0,
                    'cash_shortage_threshold' => 0,
                    'pending_approval_days' => 3650,
                    'document_expiry_days' => 0,
                ]);

                $taskAutomation->process($asOf);
                $this->seedManualTask($arusha, $arushaManager, $arushaLoanOfficer, $asOf);
                $this->seedAppointments($dar, $arusha, $darCustomer, $arushaCustomer, $darManager, $darLoanOfficer, $arushaManager, $arushaLoanOfficer, $asOf);
                $this->seedReminder($business, $overdueLoan, $reminders, $asOf);
                $alerts->process($asOf);
                $this->seedSecurityAttempts($darLoanOfficer, $loginAttempts, $asOf);

                $this->assertFinancialControl('after');
            });
        } finally {
            Auth::logout();
            CarbonImmutable::setTestNow($previousTestNow);
        }
    }

    private function seedManualTask(Branch $branch, User $manager, User $assignee, CarbonImmutable $asOf): void
    {
        $task = Task::query()->where('automation_key', SimulationOperationsSurfacesExpectations::ARUSHA_TASK_KEY)->first();
        if ($task) {
            return;
        }

        $this->asActor($manager, function () use ($branch, $manager, $assignee, $asOf, &$task): void {
            $task = Task::query()->create([
                'branch_id' => $branch->id,
                'title' => SimulationOperationsSurfacesExpectations::ARUSHA_TASK_TITLE,
                'description' => 'Review the simulation customer document pack and record the operational outcome.',
                'category' => 'document',
                'priority' => 'normal',
                'status' => 'pending',
                'due_at' => $asOf->setTime(10, 0),
                'assigned_to' => $assignee->id,
                'created_by' => $manager->id,
                'updated_by' => $manager->id,
                'automation_key' => SimulationOperationsSurfacesExpectations::ARUSHA_TASK_KEY,
            ]);
            AuditService::record('task.created', $task, [], $task->toArray());
        });

        $this->asActor($assignee, function () use ($task, $assignee, $asOf): void {
            $before = $task->toArray();
            $task->update([
                'status' => 'completed',
                'updated_by' => $assignee->id,
                'completed_by' => $assignee->id,
                'completed_at' => $asOf->setTime(10, 30),
            ]);
            AuditService::record('task.status_changed', $task, $before, $task->fresh()->toArray());
        });
    }

    private function seedAppointments(
        Branch $dar,
        Branch $arusha,
        Customer $darCustomer,
        Customer $arushaCustomer,
        User $darManager,
        User $darLoanOfficer,
        User $arushaManager,
        User $arushaLoanOfficer,
        CarbonImmutable $asOf,
    ): void {
        if (! CustomerAppointment::query()->where('title', SimulationOperationsSurfacesExpectations::DAR_APPOINTMENT_TITLE)->where('branch_id', $dar->id)->exists()) {
            $this->asActor($darManager, function () use ($dar, $darCustomer, $darLoanOfficer, $darManager, $asOf): void {
                $appointment = CustomerAppointment::query()->create([
                    'branch_id' => $dar->id,
                    'customer_id' => $darCustomer->id,
                    'staff_id' => $darLoanOfficer->id,
                    'title' => SimulationOperationsSurfacesExpectations::DAR_APPOINTMENT_TITLE,
                    'purpose' => 'Repayment follow-up',
                    'scheduled_at' => $asOf->setTime(14, 0),
                    'status' => 'scheduled',
                    'notes' => 'Deterministic Phase 5 customer follow-up appointment.',
                    'created_by' => $darManager->id,
                    'updated_by' => $darManager->id,
                ]);
                AuditService::record('appointment.created', $appointment, [], $appointment->toArray());
            });
        }

        if (! CustomerAppointment::query()->where('title', SimulationOperationsSurfacesExpectations::ARUSHA_APPOINTMENT_TITLE)->where('branch_id', $arusha->id)->exists()) {
            $appointment = null;
            $this->asActor($arushaManager, function () use ($arusha, $arushaCustomer, $arushaLoanOfficer, $arushaManager, $asOf, &$appointment): void {
                $appointment = CustomerAppointment::query()->create([
                    'branch_id' => $arusha->id,
                    'customer_id' => $arushaCustomer->id,
                    'staff_id' => $arushaLoanOfficer->id,
                    'title' => SimulationOperationsSurfacesExpectations::ARUSHA_APPOINTMENT_TITLE,
                    'purpose' => 'Portfolio review',
                    'scheduled_at' => $asOf->setTime(11, 0),
                    'status' => 'scheduled',
                    'notes' => 'Deterministic Phase 5 completed appointment.',
                    'created_by' => $arushaManager->id,
                    'updated_by' => $arushaManager->id,
                ]);
                AuditService::record('appointment.created', $appointment, [], $appointment->toArray());
            });

            $this->asActor($arushaLoanOfficer, function () use ($appointment, $arushaLoanOfficer): void {
                $before = $appointment->toArray();
                $appointment->update(['status' => 'completed', 'updated_by' => $arushaLoanOfficer->id]);
                AuditService::record('appointment.status_changed', $appointment, $before, $appointment->fresh()->toArray());
            });
        }
    }

    private function seedReminder(Business $business, Loan $overdueLoan, LoanReminderService $reminders, CarbonImmutable $asOf): void
    {
        $business->update([
            'reminders_enabled' => true,
            'reminder_pre_due_days' => [],
            'reminder_due_date_enabled' => false,
            'reminder_overdue_enabled' => true,
            'reminder_overdue_frequency' => 'weekly',
            'reminder_channels' => ['in_system'],
        ]);

        NotificationTemplate::query()->updateOrCreate(
            ['business_id' => $business->id, 'event_type' => 'overdue', 'channel' => 'in_system'],
            [
                'subject' => 'Overdue payment reminder · {loan_number}',
                'body' => 'Hello {customer_name}, loan {loan_number} has {amount_due} overdue by {days_overdue} day(s). Please contact {branch_name}.',
                'active' => true,
            ],
        );

        if ((int) $overdueLoan->days_overdue !== SimulationOperationsSurfacesExpectations::OVERDUE_DAYS_THRESHOLD) {
            throw new RuntimeException('Phase 5 overdue reminder control requires the deterministic 43-day overdue loan.');
        }

        $reminders->dispatch($asOf);
    }

    private function seedSecurityAttempts(User $successfulUser, LoginAttemptService $service, CarbonImmutable $asOf): void
    {
        $successAt = $asOf->setTime(12, 5);
        if (! LoginAttempt::query()->where('login_identifier', $successfulUser->username)->where('status', 'success')->where('attempted_at', $successAt)->exists()) {
            CarbonImmutable::setTestNow($successAt);
            $request = Request::create('/login', 'POST', [], [], [], [
                'REMOTE_ADDR' => '10.10.0.11',
                'HTTP_USER_AGENT' => 'SimulationPhase5/Success',
            ]);
            $service->record($request, 'success', $successfulUser->username, $successfulUser);
        }

        $failedAt = $asOf->setTime(12, 6);
        if (! LoginAttempt::query()->where('login_identifier', SimulationOperationsSurfacesExpectations::FAILED_LOGIN_IDENTIFIER)->where('status', 'failed')->where('attempted_at', $failedAt)->exists()) {
            CarbonImmutable::setTestNow($failedAt);
            $request = Request::create('/login', 'POST', [], [], [], [
                'REMOTE_ADDR' => '10.10.0.99',
                'HTTP_USER_AGENT' => 'SimulationPhase5/Failure',
            ]);
            $service->record($request, 'failed', SimulationOperationsSurfacesExpectations::FAILED_LOGIN_IDENTIFIER, null);
        }

        CarbonImmutable::setTestNow($asOf);
    }

    private function asActor(User $user, callable $callback): void
    {
        $previous = Auth::user();
        Auth::login($user);
        try {
            $callback();
        } finally {
            if ($previous) {
                Auth::login($previous);
            } else {
                Auth::logout();
            }
        }
    }

    private function assertFinancialControl(string $stage): void
    {
        $actual = number_format((float) FinancialAccount::query()->where('name', 'like', 'SIM %')->sum('current_balance'), 2, '.', '');
        if ($actual !== SimulationOperationsSurfacesExpectations::FINANCIAL_CONTROL) {
            throw new RuntimeException('Phase 5 financial control mismatch '.$stage.': expected '.SimulationOperationsSurfacesExpectations::FINANCIAL_CONTROL.', got '.$actual.'.');
        }
    }
}
