<?php

namespace Database\Seeders\Simulation;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\FinancialAccount;
use App\Models\LoanProduct;
use App\Models\Role;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Services\UserBranchAssignmentService;
use App\Support\Simulation\SimulationExpectations;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class SimulationFoundationSeeder extends Seeder
{
    public function run(
        AuthorizationAssignmentService $authorization,
        UserBranchAssignmentService $branchAssignments,
    ): void {
        DB::transaction(function () use ($authorization, $branchAssignments): void {
            $business = Business::query()->firstOrFail();
            $anchor = CarbonImmutable::parse((string) config('simulation.anchor_date', SimulationExpectations::ANCHOR_DATE));

            $branches = $this->seedBranches($business, $anchor);
            $staff = $this->seedStaff($branches, $authorization, $branchAssignments, $anchor);
            $this->assignBranchManagers($branches, $staff);
            $this->seedAccounts($branches, $staff);
            $this->seedCustomers($branches, $staff, $anchor);
            $this->seedLoanProducts($business, $staff);
        });
    }

    /** @return array<string, Branch> */
    private function seedBranches(Business $business, CarbonImmutable $anchor): array
    {
        $branches = [];

        foreach (SimulationExpectations::branches() as $key => $definition) {
            $branches[$key] = Branch::query()->updateOrCreate(
                ['business_id' => $business->id, 'code' => $definition['code']],
                [
                    'name' => $definition['name'],
                    'location' => $definition['location'],
                    'address' => $definition['location'].' simulation office',
                    'region' => $definition['region'],
                    'district' => $definition['district'],
                    'phone' => $definition['phone'],
                    'email' => $definition['email'],
                    'opening_date' => $anchor->subMonths(7)->toDateString(),
                    'status' => 'active',
                ],
            );
        }

        return $branches;
    }

    /**
     * @param array<string, Branch> $branches
     * @return array<string, User>
     */
    private function seedStaff(
        array $branches,
        AuthorizationAssignmentService $authorization,
        UserBranchAssignmentService $branchAssignments,
        CarbonImmutable $anchor,
    ): array {
        $staff = [];
        $password = (string) config('simulation.user_password', 'Simulation@2026');
        $allBranchIds = collect($branches)->pluck('id')->map(fn ($id) => (int) $id)->all();

        foreach (SimulationExpectations::staff() as $index => $definition) {
            $primary = $branches[$definition['branch']];
            $user = User::query()->updateOrCreate(
                ['username' => $definition['username']],
                [
                    'name' => $definition['name'],
                    'email' => str_replace('.', '-', $definition['username']).'@example.test',
                    'phone' => '0731'.str_pad((string) ($index + 1), 6, '0', STR_PAD_LEFT),
                    'position' => $definition['position'],
                    'status' => 'active',
                    'employed_at' => $anchor->subMonths(6)->toDateString(),
                    'primary_branch_id' => $primary->id,
                    'password' => $password,
                ],
            );

            $assignedBranchIds = ($definition['all_branches'] ?? false)
                ? $allBranchIds
                : [$primary->id];

            $branchAssignments->synchronize($user, (int) $primary->id, $assignedBranchIds);

            $role = Role::query()->where('name', $definition['role'])->firstOrFail();
            $authorization->syncUserRoleIds($user, [$role->id]);

            $staff[$definition['username']] = $user->fresh(['roles', 'branches']);
        }

        return $staff;
    }

    /** @param array<string, Branch> $branches @param array<string, User> $staff */
    private function assignBranchManagers(array $branches, array $staff): void
    {
        $managerByBranch = [
            'dar' => 'sim.dar.manager',
            'mwanza' => 'sim.mwanza.manager',
            'arusha' => 'sim.arusha.manager',
        ];

        foreach ($managerByBranch as $branchKey => $username) {
            $branches[$branchKey]->update(['manager_user_id' => $staff[$username]->id]);
        }
    }

    /** @param array<string, Branch> $branches @param array<string, User> $staff */
    private function seedAccounts(array $branches, array $staff): void
    {
        $creatorByBranch = [
            'dar' => $staff['sim.dar.accountant'],
            'mwanza' => $staff['sim.mwanza.manager'],
            'arusha' => $staff['sim.arusha.manager'],
        ];

        foreach (SimulationExpectations::accounts() as $branchKey => $definitions) {
            foreach ($definitions as $definition) {
                $account = FinancialAccount::query()->firstOrNew([
                    'branch_id' => $branches[$branchKey]->id,
                    'name' => $definition['name'],
                ]);
                $isNew = ! $account->exists;

                $account->fill([
                    'account_type' => $definition['type'],
                    'opening_balance' => $definition['opening'],
                    'status' => 'active',
                    'reference_details' => 'Deterministic full-system simulation opening control',
                    'created_by' => $account->created_by ?: $creatorByBranch[$branchKey]->id,
                    'updated_by' => $creatorByBranch[$branchKey]->id,
                ]);

                // Transactional phases own current_balance after the account is created.
                // Never reset it during an idempotent simulation rerun.
                if ($isNew) {
                    $account->current_balance = $definition['opening'];
                }

                $account->save();
            }
        }
    }

    /** @param array<string, Branch> $branches @param array<string, User> $staff */
    private function seedCustomers(array $branches, array $staff, CarbonImmutable $anchor): void
    {
        $creatorByBranch = [
            'dar' => $staff['sim.dar.loan'],
            'mwanza' => $staff['sim.mwanza.loan'],
            'arusha' => $staff['sim.arusha.loan'],
        ];

        foreach (SimulationExpectations::customers() as $index => $definition) {
            $creator = $creatorByBranch[$definition['branch']];

            Customer::query()->updateOrCreate(
                ['customer_number' => $definition['number']],
                [
                    'branch_id' => $branches[$definition['branch']]->id,
                    'identity_mode' => $definition['identity_mode'],
                    'first_name' => $definition['first_name'],
                    'last_name' => $definition['last_name'],
                    'gender' => $definition['gender'],
                    'date_of_birth' => $anchor->subYears(25 + ($index % 20))->subDays($index)->toDateString(),
                    'nin' => $definition['nin'],
                    'phone' => $definition['phone'],
                    'email' => strtolower($definition['number']).'@example.test',
                    'occupation' => $definition['occupation'],
                    'monthly_income' => $definition['monthly_income'],
                    'marital_status' => $index % 2 === 0 ? 'single' : 'married',
                    'status' => $definition['status'],
                    'created_by' => $creator->id,
                    'updated_by' => $creator->id,
                ],
            );
        }
    }

    /** @param array<string, User> $staff */
    private function seedLoanProducts(Business $business, array $staff): void
    {
        foreach (SimulationExpectations::loanProducts() as $definition) {
            LoanProduct::query()->updateOrCreate(
                ['business_id' => $business->id, 'name' => $definition['name']],
                $definition + [
                    'payment_deadline_enabled' => false,
                    'payment_deadline_time' => null,
                    'status' => 'active',
                    'created_by' => $staff['sim.dar.manager']->id,
                    'updated_by' => $staff['sim.dar.manager']->id,
                ],
            );
        }
    }
}
