<?php

namespace App\Services\QuickBooks;

use App\Models\Account;
use App\Models\Customer;
use App\Models\Employee;
use App\Models\FiscalYear;
use App\Models\OtherName;
use App\Models\Vendor;
use App\Services\FiscalYearService;
use Illuminate\Support\Collection;

class QuickBooksReferenceRegistry
{
    /** @var array<string, int> */
    private array $accounts = [];

    /** @var array<string, array{id: int, type: string}> */
    private array $parties = [];

    /** @var Collection<int, FiscalYear> */
    private Collection $fiscalYears;

    public function __construct(private readonly int $companyId)
    {
        $this->load();
    }

    public function accountId(string $name): ?int
    {
        return $this->accounts[$this->key($name)] ?? null;
    }

    /**
     * @return array{id: int, type: string}|null
     */
    public function party(string $name): ?array
    {
        return $this->parties[$this->key($name)] ?? null;
    }

    public function fiscalYearId(?\DateTimeInterface $date): ?int
    {
        $service = app(FiscalYearService::class);

        try {
            $fiscalYear = $date
                ? $service->resolveForDate($this->companyId, $date)
                : $service->current($this->companyId);
        } catch (\RuntimeException) {
            return $this->fiscalYears->firstWhere('is_current', true)?->id
                ?? $this->fiscalYears->first()?->id;
        }

        if ($fiscalYear && !$this->fiscalYears->contains('id', $fiscalYear->id)) {
            $this->fiscalYears->prepend($fiscalYear);
        }

        return $fiscalYear?->id;
    }

    /**
     * @param array<int, array<string, mixed>> $transactions
     * @return array{
     *   accounts: array<int, array{name: string, account_type: ?string}>,
     *   parties: array<int, string>
     * }
     */
    public function missingFromTransactions(array $transactions): array
    {
        $missingAccounts = [];
        $missingParties = [];

        foreach ($transactions as $transaction) {
            foreach ($transaction['lines'] as $line) {
                $account = trim((string) $line['account']);
                if ($account === '' || $this->accountId($account)) {
                    continue;
                }

                $accountKey = $this->key($account);
                $accountType = trim((string) ($line['account_type'] ?? '')) ?: null;

                if (!isset($missingAccounts[$accountKey])) {
                    $missingAccounts[$accountKey] = [
                        'name' => $account,
                        'account_type' => $accountType,
                    ];
                    continue;
                }

                if (!$missingAccounts[$accountKey]['account_type'] && $accountType) {
                    $missingAccounts[$accountKey]['account_type'] = $accountType;
                }
            }

            $party = trim((string) ($transaction['party_name'] ?? ''));
            if ($party !== '' && !$this->party($party)) {
                $missingParties[$this->key($party)] = $party;
            }
        }

        return [
            'accounts' => array_values($missingAccounts),
            'parties'  => array_values($missingParties),
        ];
    }

    public function registerAccount(string $name, int $id): void
    {
        $this->accounts[$this->key($name)] = $id;
    }

    public function registerParty(string $name, int $id, string $type): void
    {
        $this->parties[$this->key($name)] = ['id' => $id, 'type' => $type];
    }

    private function load(): void
    {
        $this->accounts = Account::withoutGlobalScopes()
            ->where('company_id', $this->companyId)
            ->whereNull('deleted_at')
            ->pluck('id', 'name')
            ->mapWithKeys(fn($id, $name) => [$this->key($name) => (int) $id])
            ->all();

        foreach ([
            ['model' => Customer::class, 'type' => 'customer'],
            ['model' => Vendor::class, 'type' => 'vendor'],
            ['model' => Employee::class, 'type' => 'employee'],
            ['model' => OtherName::class, 'type' => 'other_name'],
        ] as $source) {
            $source['model']::withoutGlobalScopes()
                ->where('company_id', $this->companyId)
                ->whereNull('deleted_at')
                ->get(['id', 'name'])
                ->each(function ($row) use ($source) {
                    $this->parties[$this->key($row->name)] = [
                        'id' => (int) $row->id,
                        'type' => $source['type'],
                    ];
                });
        }

        $this->fiscalYears = FiscalYear::withoutGlobalScopes()
            ->where('company_id', $this->companyId)
            ->orderByDesc('is_current')
            ->orderByDesc('start_date')
            ->get();
    }

    private function key(string $value): string
    {
        return strtolower(trim($value));
    }
}
