<?php

declare(strict_types=1);

namespace App\Business\Settlement;

use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Models\DberpPayableRecord;
use App\Models\DberpReceivableRecord;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Collection;

final class SettlementAgingAnalysisService
{
    /**
     * @param  array<string, mixed>  $filters
     * @return array{rows: list<array<string, mixed>>, totals: array<string, float>}
     */
    public function analyze(array $filters): array
    {
        $normalized = $this->normalizeFilters($filters);
        $settlementSide = $normalized['settlement_side'];
        $asOfDate = now()->parse($normalized['as_of_date'])->startOfDay();
        $counterpartyId = $normalized['counterparty_id'];

        $records = $settlementSide === 'payable'
            ? $this->payableRecords($counterpartyId)
            : $this->receivableRecords($counterpartyId);

        $rows = [];
        $totals = [
            'current_amount' => 0.0,
            'bucket_1_30_amount' => 0.0,
            'bucket_31_60_amount' => 0.0,
            'bucket_61_90_amount' => 0.0,
            'bucket_91_plus_amount' => 0.0,
            'total_amount' => 0.0,
        ];

        foreach ($records as $record) {
            $counterpartyKey = $settlementSide === 'payable' ? (int) $record->supplier_id : (int) $record->customer_id;
            $counterpartyName = $settlementSide === 'payable'
                ? (string) $record->supplier?->name
                : (string) $record->customer?->name;

            $row = $rows[$counterpartyKey] ?? [
                'counterparty_id' => $counterpartyKey,
                'counterparty_name' => $counterpartyName,
                'record_count' => 0,
                'current_amount' => 0.0,
                'bucket_1_30_amount' => 0.0,
                'bucket_31_60_amount' => 0.0,
                'bucket_61_90_amount' => 0.0,
                'bucket_91_plus_amount' => 0.0,
                'total_amount' => 0.0,
            ];

            $amount = (float) $record->remaining_amount;
            $bucket = $this->resolveBucket($record->due_date ?? $record->business_date, $asOfDate);

            $row[$bucket] += $amount;
            $row['record_count']++;
            $row['total_amount'] += $amount;

            $totals[$bucket] += $amount;
            $totals['total_amount'] += $amount;

            $rows[$counterpartyKey] = $row;
        }

        $sortedRows = array_values($rows);
        usort($sortedRows, fn (array $left, array $right): int => $right['total_amount'] <=> $left['total_amount']);

        return [
            'rows' => $sortedRows,
            'totals' => $totals,
        ];
    }

    /**
     * @param  array<string, mixed>  $formState
     * @return array{settlement_side: string, as_of_date: string, counterparty_id: int}
     */
    public function normalizeFilters(array $formState): array
    {
        $settlementSide = (string) ($formState['settlement_side'] ?? 'receivable');
        if (! in_array($settlementSide, ['receivable', 'payable'], true)) {
            $settlementSide = 'receivable';
        }

        $asOfDate = (string) ($formState['as_of_date'] ?? now()->toDateString());
        if ($asOfDate === '') {
            $asOfDate = now()->toDateString();
        }

        return [
            'settlement_side' => $settlementSide,
            'as_of_date' => $asOfDate,
            'counterparty_id' => max(0, (int) ($formState['counterparty_id'] ?? 0)),
        ];
    }

    /**
     * @return Collection<int, DberpPayableRecord>
     */
    private function payableRecords(int $counterpartyId): Collection
    {
        return DberpPayableRecord::query()
            ->with('supplier')
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', SettlementRecordStatus::Voided->value)
            ->when($counterpartyId > 0, fn ($query) => $query->where('supplier_id', $counterpartyId))
            ->orderBy('due_date')
            ->orderBy('id')
            ->get();
    }

    /**
     * @return Collection<int, DberpReceivableRecord>
     */
    private function receivableRecords(int $counterpartyId): Collection
    {
        return DberpReceivableRecord::query()
            ->with('customer')
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', SettlementRecordStatus::Voided->value)
            ->when($counterpartyId > 0, fn ($query) => $query->where('customer_id', $counterpartyId))
            ->orderBy('due_date')
            ->orderBy('id')
            ->get();
    }

    private function resolveBucket(mixed $dueDate, CarbonInterface $asOfDate): string
    {
        $baseDate = $dueDate instanceof CarbonInterface ? $dueDate->copy()->startOfDay() : now()->parse((string) $dueDate)->startOfDay();

        if ($baseDate->greaterThanOrEqualTo($asOfDate)) {
            return 'current_amount';
        }

        $days = $baseDate->diffInDays($asOfDate);

        return match (true) {
            $days <= 30 => 'bucket_1_30_amount',
            $days <= 60 => 'bucket_31_60_amount',
            $days <= 90 => 'bucket_61_90_amount',
            default => 'bucket_91_plus_amount',
        };
    }
}
