<?php

namespace App\Services\Payments;

use App\Models\Configuration;
use App\Models\PaymentNotificationLog;
use App\Models\User;
use App\Services\EmailService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PaymentNotificationService
{
    public function __construct(
        private EmailService $emailService,
        private PaymentNotificationDispatchService $dispatchService
    ) {
    }

    public function listLogs(?string $from, ?string $to, ?string $type): array
    {
        $query = PaymentNotificationLog::query()->with('parent')->orderByDesc('sent_at');

        if (!empty($from)) {
            $query->where('sent_at', '>=', $from);
        }
        if (!empty($to)) {
            $query->where('sent_at', '<=', $to);
        }
        if (!empty($type)) {
            $query->where('type', $type);
        }

        return $query->get()->all();
    }

    public function send(array $payload): array
    {
        $parentId = (int) ($payload['parent_id'] ?? 0);
        $typeInput = (string) ($payload['type'] ?? '');
        $force = (bool) ($payload['force'] ?? false);
        $schoolYear = (string) ($payload['school_year'] ?? (Configuration::getConfig('school_year') ?? date('Y')));

        $tzName = $this->getTimezone();
        $tz = new \DateTimeZone($tzName);
        $now = new \DateTime('now', $tz);
        $year = (int) $now->format('Y');
        $month = (int) $now->format('n');

        $results = [
            'sent' => 0,
            'skipped' => 0,
            'failed' => 0,
            'details' => [],
        ];

        if ($parentId > 0) {
            $result = $this->sendToParent($parentId, $typeInput, $schoolYear, $year, $month, $now, $force, false);
            $results = $this->mergeResults($results, $result);
        } else {
            $parentIds = $this->getParentsWithInvoices($schoolYear);
            foreach ($parentIds as $pid) {
                $result = $this->sendToParent($pid, $typeInput, $schoolYear, $year, $month, $now, $force, true);
                $results = $this->mergeResults($results, $result);
            }
        }

        return $results;
    }

    private function sendToParent(
        int $parentId,
        string $typeInput,
        string $schoolYear,
        int $year,
        int $month,
        \DateTime $now,
        bool $force,
        bool $notifyHeads
    ): array {
        $results = ['sent' => 0, 'skipped' => 0, 'failed' => 0, 'details' => []];

        [$balance, $latestInv] = $this->computeBalance($parentId, $schoolYear);

        $type = $this->resolveType($parentId, $schoolYear, $typeInput);

        if (!$force && PaymentNotificationLog::existsForPeriod($parentId, $year, $month, $type)) {
            $results['skipped']++;
            $results['details'][] = ['parent_id' => $parentId, 'result' => 'already_sent'];
            return $results;
        }

        if ($balance <= 0 && !$force) {
            $results['skipped']++;
            $results['details'][] = ['parent_id' => $parentId, 'result' => 'no_balance'];
            return $results;
        }

        [$subject, $body] = $this->composeMessage($parentId, $balance, $schoolYear, $now);
        $installmentInfo = $this->computeInstallmentSuggestion($balance);

        $toEmail = $this->getUserEmail($parentId);
        $ccEmail = $this->getSecondaryGuardianEmail($parentId);

        $ok = $toEmail ? $this->emailService->send($toEmail, $subject, $body, 'finance') : false;
        if ($ok && $ccEmail && strcasecmp($ccEmail, (string) $toEmail) !== 0) {
            $this->emailService->send($ccEmail, $subject, $body, 'finance');
        }

        PaymentNotificationLog::query()->create([
            'parent_id' => $parentId,
            'invoice_id' => $latestInv,
            'school_year' => $schoolYear,
            'period_year' => $year,
            'period_month' => $month,
            'type' => $type,
            'to_email' => $toEmail,
            'cc_email' => $ccEmail,
            'head_fa_notified' => $notifyHeads ? 1 : 0,
            'subject' => $subject,
            'body' => $body,
            'status' => $ok ? 'sent' : 'failed',
            'error_message' => $ok ? null : 'Missing/invalid email or send failed',
            'balance_snapshot' => $balance,
            'sent_at' => $now->format('Y-m-d H:i:s'),
        ]);

        if ($ok) {
            $results['sent']++;
            $results['details'][] = [
                'parent_id' => $parentId,
                'result' => 'sent',
                'balance' => $balance,
                'installment_due' => $installmentInfo['installment_due'],
                'remaining' => $installmentInfo['remaining_installments'],
            ];
        } else {
            $results['failed']++;
            $results['details'][] = ['parent_id' => $parentId, 'result' => 'failed'];
        }

        if ($notifyHeads) {
            $this->notifyHeadsOfFinance($type, $parentId, $balance);
        }

        return $results;
    }

    private function computeBalance(int $parentId, string $schoolYear): array
    {
        $rows = DB::table('invoices')
            ->select('id', 'total_amount')
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        $hasStatus = DB::getSchemaBuilder()->hasColumn('payments', 'status');
        $hasVoid = DB::getSchemaBuilder()->hasColumn('payments', 'is_void');

        $sumBalance = 0.0;
        $latestId = null;

        foreach ($rows as $row) {
            $invoiceId = (int) ($row['id'] ?? 0);
            if ($invoiceId <= 0) {
                continue;
            }
            if ($latestId === null) {
                $latestId = $invoiceId;
            }

            $total = (float) ($row['total_amount'] ?? 0);

            $qb = DB::table('payments')
                ->selectRaw('COALESCE(SUM(paid_amount),0) AS total')
                ->where('invoice_id', $invoiceId);

            if ($hasStatus) {
                $qb->where(function ($q) {
                    $q->whereNotIn('status', [
                        'void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled',
                    ])->orWhereNull('status');
                });
            }

            if ($hasVoid) {
                $qb->where(function ($q) {
                    $q->where('is_void', 0)->orWhereNull('is_void');
                });
            }

            $paid = (float) (optional($qb->first())->total ?? 0);

            $disc = (float) (optional(DB::table('discount_usages')
                ->selectRaw('COALESCE(SUM(discount_amount),0) AS total')
                ->where('invoice_id', $invoiceId)
                ->first())->total ?? 0);

            $rfnd = (float) (optional(DB::table('refunds')
                ->selectRaw('COALESCE(SUM(refund_paid_amount),0) AS total')
                ->where('invoice_id', $invoiceId)
                ->whereIn('status', ['Partial', 'Paid'])
                ->first())->total ?? 0);

            $sumBalance += max(0.0, round($total - $disc - $paid - $rfnd, 2));
        }

        return [$sumBalance, $latestId];
    }

    private function resolveType(int $parentId, string $schoolYear, string $typeInput): string
    {
        if (in_array($typeInput, ['no_payment', 'installment'], true)) {
            return $typeInput;
        }

        $hasPayments = DB::table('payments')
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->count() > 0;

        return $hasPayments ? 'installment' : 'no_payment';
    }

    private function composeMessage(int $parentId, float $balance, string $schoolYear, \DateTime $now): array
    {
        $user = User::query()->find($parentId);
        $parentName = $user ? trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')) : '';
        $greeting = $parentName !== '' ? "Dear {$parentName}," : 'Dear Parent,';

        $monthYear = $now->format('F Y');
        $subject = sprintf('Monthly Tuition Reminder — %s', $schoolYear);
        $balanceFmt = '$' . number_format($balance, 2);
        $intro = 'This is your monthly installment reminder for ' . $schoolYear . '.';

        $installmentInfo = $this->computeInstallmentSuggestion($balance);

        $bodyHtml = <<<HTML
            <div style="font-family:Arial,Helvetica,sans-serif; font-size:14px; color:#212529; line-height:1.5;">
              <h2 style="margin:0 0 10px; font-size:18px;">Monthly Tuition Reminder</h2>
              <p>{$greeting}</p>
              <p>{$intro}</p>
              <p><strong>Current Outstanding Balance:</strong> {$balanceFmt}</p>
              <ul>
                <li><strong>Remaining installments:</strong> {$installmentInfo['remaining_installments']}</li>
                <li><strong>Suggested installment this month:</strong> {$installmentInfo['installment_due_fmt']}</li>
                <li><strong>Maximum installments available:</strong> {$installmentInfo['max_installments']}</li>
              </ul>
              <p>As a friendly reminder, our tuition installments are due at the beginning of each month.</p>
              <p>You can review your invoice and payment options by logging in to your parent portal.</p>
              <p>Thank you for your prompt attention.</p>
              <p><em>Reminder Period:</em> {$monthYear}</p>
            </div>
        HTML;

        if (view()->exists('emails._wrap_layout')) {
            $body = view('emails._wrap_layout', [
                'title' => 'Monthly Tuition Reminder',
                'body_html' => $bodyHtml,
            ], ['saveData' => true]);
        } else {
            $body = $bodyHtml;
        }

        return [$subject, $body];
    }

    private function computeInstallmentSuggestion(float $balance): array
    {
        $installmentEndRaw = (string) (Configuration::getConfig('installment_date') ?? '');
        $tzName = $this->getTimezone();
        $tz = new \DateTimeZone($tzName);
        $today = new \DateTimeImmutable('today', $tz);

        $end = null;
        if ($installmentEndRaw !== '') {
            try {
                $end = new \DateTimeImmutable($installmentEndRaw, $tz);
            } catch (\Throwable $e) {
                $end = null;
            }
        }

        $remMonths = 0;
        if ($end) {
            $y = (int) $end->format('Y') - (int) $today->format('Y');
            $m = (int) $end->format('n') - (int) $today->format('n');
            $remMonths = $y * 12 + $m;
            if ((int) $end->format('j') > (int) $today->format('j')) {
                $remMonths += 1;
            }
            if ($remMonths < 0) {
                $remMonths = 0;
            }
        }

        $remainingInstallments = max(1, $remMonths);
        $maxInstallments = max($remMonths ?: 0, $balance > 0 ? 2 : 0);
        $installmentDue = $remainingInstallments > 0 ? ($balance / $remainingInstallments) : 0;

        return [
            'remaining_installments' => $remainingInstallments,
            'max_installments' => $maxInstallments,
            'installment_due' => round($installmentDue, 2),
            'installment_due_fmt' => '$' . number_format($installmentDue, 2),
        ];
    }

    private function getSecondaryGuardianEmail(int $primaryUserId): ?string
    {
        $row = DB::table('family_guardians')->where('user_id', $primaryUserId)->first();
        if (!$row || empty($row->family_id)) {
            return null;
        }

        $others = DB::table('family_guardians')
            ->where('family_id', (int) $row->family_id)
            ->where('user_id', '!=', $primaryUserId)
            ->where('receive_emails', 1)
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();

        foreach ($others as $g) {
            $email = (string) (DB::table('users')->where('id', (int) ($g['user_id'] ?? 0))->value('email') ?? '');
            if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
                return $email;
            }
        }

        return null;
    }

    private function getUserEmail(int $userId): ?string
    {
        $email = (string) (DB::table('users')->where('id', $userId)->value('email') ?? '');
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return null;
        }
        return $email;
    }

    private function notifyHeadsOfFinance(string $type, int $parentId, float $balance): void
    {
        try {
            $heads = User::getUsersByRole('head of department (finance)');
            if (empty($heads)) {
                $heads = User::getUsersByRole('accountant');
            }

            foreach ($heads as $user) {
                $this->dispatchService->notifyUser(
                    (int) ($user['id'] ?? 0),
                    'Payment Reminder Sent',
                    sprintf('A %s reminder was sent to parent #%d (balance: $%0.2f).', $type, $parentId, $balance),
                    ['in_app']
                );
            }
        } catch (\Throwable $e) {
            Log::warning('Unable to notify heads of finance: ' . $e->getMessage());
        }
    }

    private function mergeResults(array $base, array $update): array
    {
        $base['sent'] += $update['sent'] ?? 0;
        $base['skipped'] += $update['skipped'] ?? 0;
        $base['failed'] += $update['failed'] ?? 0;
        if (!empty($update['details'])) {
            $base['details'] = array_merge($base['details'], $update['details']);
        }
        return $base;
    }

    private function getParentsWithInvoices(string $schoolYear): array
    {
        $rows = DB::table('invoices')
            ->selectRaw('DISTINCT parent_id')
            ->where('school_year', $schoolYear)
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        return array_values(array_unique(array_map(static fn ($r) => (int) ($r['parent_id'] ?? 0), $rows)));
    }

    private function getTimezone(): string
    {
        if (function_exists('user_timezone')) {
            return (string) user_timezone();
        }
        return (string) (config('app.timezone') ?: 'UTC');
    }
}
