<?php

namespace App\Services;

use App\Jobs\SendDailyBranchReportDelivery;
use App\Models\DailyBranchReport;
use App\Models\DailyBranchReportDelivery;
use App\Models\DailyBranchReportRecipient;
use App\Models\DailyClosing;
use App\Models\Setting;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;
use Throwable;

class DailyBranchReportService
{
    public function archiveAndDispatch(DailyClosing $closing): DailyBranchReport
    {
        $report = $this->archive($closing);
        $this->ensureDeliveries($report);
        $this->dispatchDeliveries($report, false);

        return $report->fresh(['branch', 'dailyClosing', 'deliveries']);
    }

    public function resend(DailyBranchReport $report): DailyBranchReport
    {
        $this->ensureDeliveries($report);
        $this->dispatchDeliveries($report, true);

        return $report->fresh(['branch', 'dailyClosing', 'deliveries']);
    }

    public function retry(DailyBranchReportDelivery $delivery): void
    {
        if ($delivery->status === 'sent') {
            return;
        }

        $delivery->update([
            'status' => 'pending',
            'error_message' => null,
        ]);

        try {
            SendDailyBranchReportDelivery::dispatch($delivery->id)->afterCommit();
        } catch (Throwable $exception) {
            report($exception);
        }
    }

    public function archive(DailyClosing $closing): DailyBranchReport
    {
        if ($closing->status !== 'confirmed') {
            throw new RuntimeException('Only manager-confirmed daily closings can be archived as branch reports.');
        }

        $closing->loadMissing(['business', 'branch', 'preparer', 'managerConfirmer']);

        $existing = DailyBranchReport::query()->where('daily_closing_id', $closing->id)->first();
        if ($existing) {
            return $existing;
        }

        $snapshot = $this->snapshot($closing);
        $subject = Str::limit('Daily Branch Report - '.$snapshot['branch_name'].' - '.$snapshot['report_date'], 255, '');
        $bodyHtml = view('emails.daily-branch-report', ['report' => $snapshot])->render();
        $bodyText = $this->textBody($snapshot);
        $checksum = hash('sha256', json_encode($snapshot, JSON_THROW_ON_ERROR).'|'.$bodyHtml.'|'.$bodyText);

        return DB::transaction(function () use ($closing, $snapshot, $subject, $bodyHtml, $bodyText, $checksum): DailyBranchReport {
            return DailyBranchReport::query()->firstOrCreate(
                ['daily_closing_id' => $closing->id],
                [
                    'business_id' => $closing->business_id,
                    'branch_id' => $closing->branch_id,
                    'report_date' => $closing->closing_date->toDateString(),
                    'closing_number' => $closing->closing_number,
                    'subject' => $subject,
                    'body_html' => $bodyHtml,
                    'body_text' => $bodyText,
                    'snapshot' => $snapshot,
                    'checksum' => $checksum,
                    'generated_at' => now(),
                ],
            );
        });
    }

    /** @return Collection<int, DailyBranchReportDelivery> */
    public function ensureDeliveries(DailyBranchReport $report): Collection
    {
        $report->loadMissing(['business', 'branch']);
        $enabledChannels = $this->enabledChannels($report->business_id);

        if ($enabledChannels->isEmpty()) {
            return collect();
        }

        $recipients = DailyBranchReportRecipient::query()
            ->where('business_id', $report->business_id)
            ->where('is_active', true)
            ->whereIn('channel', $enabledChannels)
            ->where(function ($query) use ($report): void {
                $query->whereNull('branch_id')->orWhere('branch_id', $report->branch_id);
            })
            ->orderBy('channel')
            ->orderBy('name')
            ->get();

        return $recipients->map(function (DailyBranchReportRecipient $recipient) use ($report): DailyBranchReportDelivery {
            return DailyBranchReportDelivery::query()->firstOrCreate(
                [
                    'daily_branch_report_id' => $report->id,
                    'channel' => $recipient->channel,
                    'recipient' => $recipient->destination,
                ],
                [
                    'recipient_id' => $recipient->id,
                    'recipient_name' => $recipient->name,
                    'status' => 'pending',
                ],
            );
        });
    }

    private function dispatchDeliveries(DailyBranchReport $report, bool $includeFailed): void
    {
        $statuses = $includeFailed ? ['pending', 'failed'] : ['pending'];
        $enabledChannels = $this->enabledChannels($report->business_id);
        if ($enabledChannels->isEmpty()) {
            return;
        }

        DailyBranchReportDelivery::query()
            ->where('daily_branch_report_id', $report->id)
            ->whereIn('channel', $enabledChannels)
            ->whereIn('status', $statuses)
            ->orderBy('id')
            ->get()
            ->each(function (DailyBranchReportDelivery $delivery): void {
                try {
                    SendDailyBranchReportDelivery::dispatch($delivery->id)->afterCommit();
                } catch (Throwable $exception) {
                    report($exception);
                }
            });
    }

    /** @return Collection<int, string> */
    private function enabledChannels(int $businessId): Collection
    {
        return collect(DailyBranchReportRecipient::CHANNELS)
            ->filter(fn (string $channel): bool => $this->channelEnabled($businessId, $channel))
            ->values();
    }

    private function channelEnabled(int $businessId, string $channel): bool
    {
        $value = Setting::query()
            ->where('business_id', $businessId)
            ->where('group', 'daily_reports')
            ->where('key', $channel.'_enabled')
            ->value('value');

        return (string) $value === '1';
    }

    private function snapshot(DailyClosing $closing): array
    {
        return [
            'business_id' => $closing->business_id,
            'business_name' => $closing->business?->name ?? 'Business',
            'currency' => $closing->business?->currency ?? 'TZS',
            'currency_symbol' => $closing->business?->currency_symbol ?? '',
            'branch_id' => $closing->branch_id,
            'branch_name' => $closing->branch?->name ?? 'Branch',
            'report_date' => $closing->closing_date->toDateString(),
            'closing_number' => $closing->closing_number,
            'status' => $closing->status,
            'prepared_by' => $closing->preparer?->name,
            'prepared_at' => $closing->prepared_at?->toIso8601String(),
            'confirmed_by' => $closing->managerConfirmer?->name,
            'confirmed_at' => $closing->manager_confirmed_at?->toIso8601String(),
            'manager_comments' => $closing->manager_comments,
            'opening_funds' => (string) $closing->opening_funds,
            'collections' => (string) $closing->collections,
            'cash_collections' => (string) $closing->cash_collections,
            'mobile_money_collections' => (string) $closing->mobile_money_collections,
            'bank_collections' => (string) $closing->bank_collections,
            'other_collections' => (string) $closing->other_collections,
            'disbursements' => (string) $closing->disbursements,
            'expenses' => (string) $closing->expenses,
            'transfers' => (string) $closing->transfers,
            'capital_inflows' => (string) $closing->capital_inflows,
            'other_inflows' => (string) $closing->other_inflows,
            'other_outflows' => (string) $closing->other_outflows,
            'closing_funds' => (string) $closing->closing_funds,
            'expected_balance' => (string) $closing->expected_cash,
            'actual_balance' => (string) $closing->actual_cash,
            'variance' => (string) $closing->variance,
            'closing_snapshot' => $closing->snapshot,
            'snapshot_fingerprint' => $closing->snapshot_fingerprint,
        ];
    }

    private function textBody(array $report): string
    {
        $money = static fn (mixed $value): string => number_format((float) $value, 2);
        $lines = [
            'DAILY BRANCH REPORT',
            $report['business_name'].' - '.$report['branch_name'],
            'Date: '.$report['report_date'],
            'Closing: '.$report['closing_number'],
            '',
            'Opening funds: '.$money($report['opening_funds']),
            'Collections: '.$money($report['collections']),
            '  Cash: '.$money($report['cash_collections']),
            '  Mobile money: '.$money($report['mobile_money_collections']),
            '  Bank: '.$money($report['bank_collections']),
            '  Other: '.$money($report['other_collections']),
            'Disbursements: '.$money($report['disbursements']),
            'Expenses: '.$money($report['expenses']),
            'Net transfers: '.$money($report['transfers']),
            'Capital inflows: '.$money($report['capital_inflows']),
            'Other inflows: '.$money($report['other_inflows']),
            'Other outflows: '.$money($report['other_outflows']),
            'Closing funds: '.$money($report['closing_funds']),
            '',
            'Expected reconciled balance: '.$money($report['expected_balance']),
            'Actual reconciled balance: '.$money($report['actual_balance']),
            'Variance: '.$money($report['variance']),
            '',
            'Confirmed by: '.($report['confirmed_by'] ?: 'Manager'),
        ];

        if (filled($report['manager_comments'] ?? null)) {
            $lines[] = 'Manager comments: '.$report['manager_comments'];
        }

        $lines[] = '';
        $lines[] = 'This report is generated from the manager-confirmed daily closing and archived for audit.';

        return implode("\n", $lines);
    }
}
