<?php

namespace App\Services\ImportExport\Handlers;

use App\Models\Account;
use App\Models\JournalEntry;
use App\Models\JournalLine;
use App\Models\User;
use App\Enums\JournalSource;
use App\Enums\JournalStatus;
use Illuminate\Support\Facades\DB;

class JournalEntriesHandler extends AbstractModuleHandler
{
    public function key(): string
    {
        return 'journal-entries';
    }

    public function label(): string
    {
        return 'Journal Entries';
    }

    public function permissionModule(): string
    {
        return 'accounting';
    }

    public function supportsImport(): bool
    {
        return true;
    }

    public function importColumns(): array
    {
        return [
            ['key' => 'reference_number', 'label' => 'Reference Number', 'required' => true, 'type' => 'string', 'example' => 'JE-2026-001', 'notes' => 'Groups lines into one journal entry'],
            ['key' => 'date', 'label' => 'Date', 'required' => true, 'type' => 'date', 'example' => '2026-01-15', 'notes' => 'YYYY-MM-DD'],
            ['key' => 'description', 'label' => 'Description', 'required' => true, 'type' => 'string', 'example' => 'Opening balances', 'notes' => 'Journal header description'],
            ['key' => 'account_code', 'label' => 'Account Code', 'required' => true, 'type' => 'string', 'example' => '1100', 'notes' => 'Must exist in chart of accounts'],
            ['key' => 'debit', 'label' => 'Debit', 'required' => false, 'type' => 'decimal', 'example' => '1000.00', 'notes' => 'Debit amount (leave blank if credit)'],
            ['key' => 'credit', 'label' => 'Credit', 'required' => false, 'type' => 'decimal', 'example' => '', 'notes' => 'Credit amount (leave blank if debit)'],
            ['key' => 'line_memo', 'label' => 'Line Memo', 'required' => false, 'type' => 'string', 'example' => 'Cash deposit', 'notes' => 'Optional line note'],
        ];
    }

    public function importInstructions(): array
    {
        return array_merge(parent::importInstructions(), [
            'Each journal entry may have multiple rows sharing the same reference_number.',
            'Total debits must equal total credits for each reference_number group.',
            'Each line must have either a debit OR credit amount, not both.',
            'Account codes must exist in the Chart of Accounts before import.',
        ]);
    }

    public function exportData(array $filters): array
    {
        $query = JournalLine::query()
            ->with(['journalEntry', 'account'])
            ->whereHas('journalEntry')
            ->latest('id');

        if ($from = ($filters['from'] ?? null)) {
            $query->whereHas('journalEntry', fn ($q) => $q->whereDate('date', '>=', $from));
        }

        if ($to = ($filters['to'] ?? null)) {
            $query->whereHas('journalEntry', fn ($q) => $q->whereDate('date', '<=', $to));
        }

        $lines = $query->limit(10000)->get();

        return [
            'title' => 'Journal Entries Export',
            'sheet_name' => 'Journal Lines',
            'headings' => ['Reference', 'Date', 'Description', 'Account Code', 'Account Name', 'Debit', 'Credit', 'Line Memo', 'Status'],
            'rows' => $lines->map(fn (JournalLine $line) => [
                $line->journalEntry->reference_number,
                optional($line->journalEntry->date)->format('Y-m-d'),
                $line->journalEntry->description,
                $line->account?->code,
                $line->account?->name,
                number_format((float) $line->debit_amount, 2, '.', ''),
                number_format((float) $line->credit_amount, 2, '.', ''),
                $line->line_memo,
                $line->journalEntry->status->value,
            ])->all(),
            'filters' => $this->filterSummary($filters),
            'totals' => ['Lines' => $lines->count()],
        ];
    }

    public function validateImportRow(array $row, int $rowNumber): array
    {
        $errors = array_merge(
            $this->validateRequired($row, $rowNumber, ['reference_number', 'date', 'description', 'account_code']),
            $this->validateDate($row, $rowNumber, 'date', true),
            $this->validateDecimal($row, $rowNumber, 'debit'),
            $this->validateDecimal($row, $rowNumber, 'credit'),
        );

        $debit = (float) ($row['debit'] ?? 0);
        $credit = (float) ($row['credit'] ?? 0);

        if ($debit <= 0 && $credit <= 0) {
            $errors[] = [
                'column' => 'debit',
                'value' => null,
                'message' => "Row {$rowNumber}: either debit or credit is required.",
                'suggestion' => 'Enter an amount in debit or credit column.',
            ];
        }

        if ($debit > 0 && $credit > 0) {
            $errors[] = [
                'column' => 'debit',
                'value' => $debit,
                'message' => "Row {$rowNumber}: a line cannot have both debit and credit.",
                'suggestion' => 'Use only one side per line.',
            ];
        }

        if (! Account::query()->where('code', $row['account_code'] ?? '')->exists()) {
            $errors[] = [
                'column' => 'account_code',
                'value' => $row['account_code'] ?? null,
                'message' => "Row {$rowNumber}: account code not found.",
                'suggestion' => 'Import or create the account first.',
            ];
        }

        return $errors;
    }

    public function importRow(array $row): array
    {
        return ['action' => 'create', 'message' => 'Use batch import via confirm'];
    }

    /**
     * Journal import requires grouped processing — called from ImportService extension.
     *
     * @param  list<array<string, mixed>>  $rows
     * @return array{created: int, updated: int, skipped: int, errors: list<string>}
     */
    public function importGroupedRows(array $rows, User $user): array
    {
        $created = 0;
        $skipped = 0;
        $errors = [];

        $groups = collect($rows)->groupBy('reference_number');

        foreach ($groups as $reference => $groupRows) {
            $debits = $groupRows->sum(fn ($r) => (float) ($r['debit'] ?? 0));
            $credits = $groupRows->sum(fn ($r) => (float) ($r['credit'] ?? 0));

            if (bccomp((string) $debits, (string) $credits, 2) !== 0) {
                $errors[] = "Journal {$reference}: debits ({$debits}) do not equal credits ({$credits}).";
                $skipped += $groupRows->count();

                continue;
            }

            $first = $groupRows->first();

            foreach ($groupRows as $row) {
                $rowErrors = $this->validateImportRow($row, 0);
                if ($rowErrors !== []) {
                    $errors[] = "Journal {$reference}: ".collect($rowErrors)->pluck('message')->first();
                    $skipped++;

                    continue 2;
                }
            }

            if (JournalEntry::query()->where('reference_number', $reference)->exists()) {
                $errors[] = "Journal {$reference}: reference already exists.";
                $skipped += $groupRows->count();

                continue;
            }

            try {
                DB::transaction(function () use ($groupRows, $first, $user, &$created) {
                    $entry = JournalEntry::query()->create([
                        'reference_number' => $first['reference_number'],
                        'date' => $first['date'],
                        'description' => $first['description'],
                        'source' => JournalSource::ManualAdjustment,
                        'posted_by' => $user->id,
                        'posted_at' => now(),
                        'status' => JournalStatus::Posted,
                    ]);

                    foreach ($groupRows as $row) {
                        $account = Account::query()->where('code', $row['account_code'])->firstOrFail();

                        JournalLine::query()->create([
                            'journal_entry_id' => $entry->id,
                            'account_id' => $account->id,
                            'debit_amount' => (float) ($row['debit'] ?? 0),
                            'credit_amount' => (float) ($row['credit'] ?? 0),
                            'line_memo' => $row['line_memo'] ?? null,
                        ]);
                    }

                    $created++;
                });
            } catch (\Throwable $e) {
                $errors[] = "Journal {$reference}: {$e->getMessage()}";
                $skipped += $groupRows->count();
            }
        }

        return ['created' => $created, 'updated' => 0, 'skipped' => $skipped, 'errors' => $errors];
    }
}
