<?php

namespace App\Services;

use App\Models\Business;
use App\Models\CashReconciliation;
use App\Models\Collateral;
use App\Models\CustomerDocument;
use App\Models\Expense;
use App\Models\FinancialAccount;
use App\Models\FinancialAccountTransaction;
use App\Models\Loan;
use App\Models\LoanWriteOff;
use App\Models\ManagementAlert;
use App\Models\PayrollPeriod;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;

class ManagementAlertService
{
    public function __construct(private ManagementWorkSettings $settings, private MoneyMath $money) {}

    public function process(string|CarbonImmutable|null $date = null): array
    {
        $asOf = $date instanceof CarbonImmutable ? $date->endOfDay() : CarbonImmutable::parse($date ?: now())->endOfDay();
        $created = 0;

        foreach (Business::query()->with('branches')->get() as $business) {
            $config = $this->settings->values($business);
            $branchIds = $business->branches->pluck('id')->all();
            if ($branchIds === []) {
                continue;
            }

            $created += $this->largeLoanAlerts($business, $branchIds, $config, $asOf);
            $created += $this->largeExpenseAlerts($business, $branchIds, $config, $asOf);
            $created += $this->overdueAlerts($business, $branchIds, $config, $asOf);
            $created += $this->lowBalanceAlerts($business, $branchIds, $config, $asOf);
            $created += $this->cashShortageAlerts($business, $branchIds, $config, $asOf);
            $created += $this->pendingApprovalAlerts($business, $branchIds, $config, $asOf);
            $created += $this->writeOffAlerts($business, $branchIds, $asOf);
            $created += $this->collateralReleaseAlerts($business, $branchIds, $asOf);
            $created += $this->documentExpiryAlerts($business, $branchIds, $config, $asOf);
        }

        return ['created' => $created, 'open' => ManagementAlert::whereIn('status', ['open','acknowledged'])->count()];
    }

    private function largeLoanAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        if ($config['large_loan_threshold'] <= 0) return 0;
        $count = 0;
        $loans = Loan::query()->with(['branch','customer'])->whereIn('branch_id', $branchIds)
            ->where('principal', '>=', $config['large_loan_threshold'])
            ->whereNotIn('status', ['draft','rejected','completed','fully_paid','closed_through_top_up','written_off'])
            ->where('created_at', '<=', $asOf)->get();
        foreach ($loans as $loan) {
            $count += $this->openAlert($business, $loan->branch_id, 'large_loan', 'warning', 'Large loan '.$loan->loan_number,
                'Loan '.$loan->loan_number.' for '.($loan->customer?->full_name ?: 'customer').' has principal '.$loan->principal.', meeting the configured large-loan threshold.',
                $loan, 'large_loan:'.$loan->id, $loan->created_at ?? $asOf, ['amount'=>(string)$loan->principal,'threshold'=>$config['large_loan_threshold']]);
        }
        return $count;
    }

    private function largeExpenseAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        if ($config['large_expense_threshold'] <= 0) return 0;
        $count = 0;
        $rows = Expense::query()->with('category')->whereIn('branch_id', $branchIds)->where('status','approved')
            ->where('amount','>=',$config['large_expense_threshold'])->whereDate('expense_date','<=',$asOf->toDateString())->get();
        foreach ($rows as $expense) {
            $count += $this->openAlert($business, $expense->branch_id, 'large_expense', 'warning', 'Large expense '.$expense->expense_number,
                ($expense->category?->name ?: 'Expense').' of '.$expense->amount.' meets the configured large-expense threshold.',
                $expense, 'large_expense:'.$expense->id, $expense->approved_at ?? $expense->created_at ?? $asOf,
                ['amount'=>(string)$expense->amount,'threshold'=>$config['large_expense_threshold']]);
        }
        return $count;
    }

    private function overdueAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        $count = 0;
        $loans = Loan::query()->with('customer')->whereIn('branch_id',$branchIds)->where('status','active')
            ->where('days_overdue','>=',$config['overdue_alert_days'])->get();
        foreach ($loans as $loan) {
            $severity = $loan->days_overdue >= 90 ? 'critical' : 'warning';
            $count += $this->openAlert($business, $loan->branch_id, 'overdue_threshold', $severity, 'Overdue threshold: '.$loan->loan_number,
                'Loan '.$loan->loan_number.' is '.$loan->days_overdue.' day(s) overdue with '.$loan->total_overdue.' currently overdue.',
                $loan, 'overdue_threshold:'.$loan->id.':'.$config['overdue_alert_days'], $loan->arrears_updated_at ?? $asOf,
                ['days_overdue'=>$loan->days_overdue,'overdue_amount'=>(string)$loan->total_overdue,'threshold_days'=>$config['overdue_alert_days']]);
        }
        return $count;
    }

    private function lowBalanceAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        if ($config['low_account_balance_threshold'] <= 0) return 0;
        $thresholdMinor = $this->money->toMinor((string)$config['low_account_balance_threshold']);
        $count = 0;
        foreach (FinancialAccount::query()->whereIn('branch_id',$branchIds)->where('status','active')->get() as $account) {
            $transactions = FinancialAccountTransaction::query()->where('account_id',$account->id)
                ->where('transaction_date','<=',$asOf)->get(['money_in','money_out']);
            if ($transactions->isEmpty()) {
                $balanceMinor = $this->money->toSignedMinor((string)$account->opening_balance);
            } else {
                $balanceMinor = 0;
                foreach ($transactions as $transaction) {
                    $balanceMinor += $this->money->toMinor((string)$transaction->money_in);
                    $balanceMinor -= $this->money->toMinor((string)$transaction->money_out);
                }
            }
            if ($balanceMinor > $thresholdMinor) continue;
            $balance = $this->money->fromSignedMinor($balanceMinor);
            $count += $this->openAlert($business, $account->branch_id, 'low_account_balance', $balanceMinor <= 0 ? 'critical' : 'warning',
                'Low account balance: '.$account->name, 'Account '.$account->name.' has a reconstructed balance of '.$balance.', at or below the configured threshold.',
                $account, 'low_account_balance:'.$account->id, $asOf, ['balance'=>$balance,'threshold'=>$config['low_account_balance_threshold']]);
        }
        return $count;
    }

    private function cashShortageAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        $thresholdMinor = $this->money->toMinor((string)$config['cash_shortage_threshold']);
        $count = 0;
        $rows = CashReconciliation::query()->whereIn('branch_id',$branchIds)->where('status','active')
            ->whereDate('reconciliation_date','<=',$asOf->toDateString())->where('variance','<',0)->get();
        foreach ($rows as $row) {
            $shortageMinor = abs($this->money->toSignedMinor((string)$row->variance));
            if ($shortageMinor < $thresholdMinor) continue;
            $count += $this->openAlert($business, $row->branch_id, 'cash_shortage', 'critical', 'Cash shortage '.$row->reconciliation_number,
                'Cash reconciliation '.$row->reconciliation_number.' recorded a shortage of '.$this->money->fromMinor($shortageMinor).'.',
                $row, 'cash_shortage:'.$row->id, $row->reconciled_at ?? $asOf,
                ['variance'=>(string)$row->variance,'shortage'=>$this->money->fromMinor($shortageMinor)]);
        }
        return $count;
    }

    private function pendingApprovalAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        $cutoff = $asOf->subDays($config['pending_approval_days']);
        $count = 0;

        $loans = Loan::query()->whereIn('branch_id',$branchIds)->where('status','under_review')
            ->where(function ($query) use ($cutoff): void {
                $query->where('submitted_at','<=',$cutoff)->orWhere(function ($nested) use ($cutoff): void {
                    $nested->whereNull('submitted_at')->where('created_at','<=',$cutoff);
                });
            })->get();
        foreach ($loans as $loan) {
            $count += $this->pendingAlert($business, $loan->branch_id, $loan, 'Loan '.$loan->loan_number, $loan->submitted_at ?? $loan->created_at ?? $asOf);
        }

        foreach (Expense::query()->whereIn('branch_id',$branchIds)->where('status','pending')->where('created_at','<=',$cutoff)->get() as $expense) {
            $count += $this->pendingAlert($business, $expense->branch_id, $expense, 'Expense '.$expense->expense_number, $expense->created_at ?? $asOf);
        }

        foreach (PayrollPeriod::query()->whereIn('branch_id',$branchIds)->where('status','draft')->where('created_at','<=',$cutoff)->get() as $period) {
            $count += $this->pendingAlert($business, $period->branch_id, $period, 'Payroll '.$period->period_number, $period->created_at ?? $asOf);
        }

        return $count;
    }

    private function pendingAlert(Business $business, int $branchId, Model $source, string $label, mixed $occurredAt): int
    {
        $class = str_replace('\\','_',get_class($source));
        return $this->openAlert($business, $branchId, 'pending_approval', 'warning', 'Pending approval: '.$label,
            $label.' is still awaiting an approval decision.', $source, 'pending_approval:'.$class.':'.$source->getKey(), $occurredAt);
    }

    private function writeOffAlerts(Business $business, array $branchIds, CarbonImmutable $asOf): int
    {
        $count = 0;
        foreach (LoanWriteOff::query()->whereIn('branch_id',$branchIds)->where('status','pending')->get() as $writeOff) {
            $count += $this->openAlert($business, $writeOff->branch_id, 'write_off_request', 'warning', 'Write-off request '.$writeOff->write_off_number,
                'Write-off '.$writeOff->write_off_number.' is awaiting a management decision for '.$writeOff->write_off_amount.'.',
                $writeOff, 'write_off_request:'.$writeOff->id, $writeOff->requested_at ?? $asOf, ['amount'=>(string)$writeOff->write_off_amount]);
        }
        return $count;
    }

    private function collateralReleaseAlerts(Business $business, array $branchIds, CarbonImmutable $asOf): int
    {
        $count = 0;
        foreach (Collateral::query()->whereIn('branch_id',$branchIds)->where('status','ready_for_release')->get() as $collateral) {
            $count += $this->openAlert($business, $collateral->branch_id, 'collateral_ready_for_release', 'info', 'Collateral ready: '.$collateral->code,
                'Collateral '.$collateral->code.' ('.$collateral->item.') is ready for release to the customer.',
                $collateral, 'collateral_ready_for_release:'.$collateral->id, $collateral->updated_at ?? $asOf);
        }
        return $count;
    }

    private function documentExpiryAlerts(Business $business, array $branchIds, array $config, CarbonImmutable $asOf): int
    {
        $count = 0;
        $until = $asOf->addDays($config['document_expiry_days'])->toDateString();
        $documents = CustomerDocument::query()->with('customer')->whereHas('customer', fn ($q) => $q->whereIn('branch_id',$branchIds))
            ->whereNotNull('expiry_date')->whereDate('expiry_date','>=',$asOf->toDateString())->whereDate('expiry_date','<=',$until)->get();
        foreach ($documents as $document) {
            $branchId = $document->customer->branch_id;
            $count += $this->openAlert($business, $branchId, 'expiring_documents', 'warning', 'Document expiring: '.$document->document_type,
                $document->document_type.' for '.$document->customer->full_name.' expires on '.$document->expiry_date->toDateString().'.',
                $document, 'expiring_document:'.$document->id.':'.$document->expiry_date->toDateString(), $asOf,
                ['expiry_date'=>$document->expiry_date->toDateString(),'customer_id'=>$document->customer_id]);
        }
        return $count;
    }

    private function openAlert(Business $business, ?int $branchId, string $type, string $severity, string $title, string $message,
        Model $source, string $key, mixed $occurredAt, array $metadata = []): int
    {
        $existing = ManagementAlert::query()->where('alert_key',$key)->first();
        if ($existing) {
            if ($existing->status !== 'resolved') {
                $existing->update(['severity'=>$severity,'title'=>$title,'message'=>$message,'metadata'=>$metadata]);
            }
            return 0;
        }

        ManagementAlert::create([
            'business_id'=>$business->id,'branch_id'=>$branchId,'type'=>$type,'severity'=>$severity,'title'=>$title,'message'=>$message,
            'status'=>'open','source_type'=>get_class($source),'source_id'=>$source->getKey(),'alert_key'=>$key,
            'occurred_at'=>$occurredAt ?: now(),'metadata'=>$metadata,
        ]);
        return 1;
    }
}