<?php

declare(strict_types=1);

namespace App\Services\Reports;

use App\Models\Invoice;
use App\Models\PaymentTransaction;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Laravel\Cashier\Subscription;

class FinancialReportService
{
    private const CACHE_TTL = 300; // 5 minutes

    /**
     * Revenue report: transactions grouped by period, service type, plan, and gateway.
     *
     * @return array{total_revenue: float, by_period: array<int, array{period: string, amount: float}>, by_service_type: array<int, array{type: string, amount: float}>, by_gateway: array<int, array{gateway: string, amount: float}>, by_plan: array<int, array{plan: string, amount: float}>}
     */
    public function revenueReport(Carbon $startDate, Carbon $endDate, ?string $serviceType = null): array
    {
        $cacheKey = "report.revenue.{$startDate->toDateString()}.{$endDate->toDateString()}.{$serviceType}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($startDate, $endDate, $serviceType): array {
            $baseQuery = PaymentTransaction::query()
                ->where('payment_transactions.status', 'succeeded')
                ->whereBetween('payment_transactions.created_at', [$startDate->startOfDay(), $endDate->endOfDay()]);

            if ($serviceType) {
                $baseQuery->join('invoices', 'payment_transactions.invoice_id', '=', 'invoices.id')
                    ->join('subscriptions', 'invoices.subscription_id', '=', 'subscriptions.id')
                    ->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
                    ->where('plans.service_type', $serviceType);
            }

            $totalRevenue = (float) (clone $baseQuery)->sum('payment_transactions.amount');

            $byPeriod = (clone $baseQuery)
                ->selectRaw("DATE_FORMAT(payment_transactions.created_at, '%Y-%m') as period, SUM(payment_transactions.amount) as amount")
                ->groupBy('period')
                ->orderBy('period')
                ->get()
                ->map(fn ($row) => ['period' => $row->period, 'amount' => (float) $row->amount])
                ->values()
                ->toArray();

            // For service type breakdown, we always need joins
            $typeQuery = PaymentTransaction::query()
                ->where('payment_transactions.status', 'succeeded')
                ->whereBetween('payment_transactions.created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->join('invoices', 'payment_transactions.invoice_id', '=', 'invoices.id')
                ->join('subscriptions', 'invoices.subscription_id', '=', 'subscriptions.id')
                ->join('plans', 'subscriptions.plan_id', '=', 'plans.id');

            if ($serviceType) {
                $typeQuery->where('plans.service_type', $serviceType);
            }

            $byServiceType = (clone $typeQuery)
                ->selectRaw('plans.service_type as type, SUM(payment_transactions.amount) as amount')
                ->groupBy('plans.service_type')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => ['type' => $row->type, 'amount' => (float) $row->amount])
                ->values()
                ->toArray();

            $byGateway = PaymentTransaction::query()
                ->where('status', 'succeeded')
                ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->selectRaw('gateway, SUM(amount) as amount')
                ->groupBy('gateway')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => ['gateway' => $row->gateway, 'amount' => (float) $row->amount])
                ->values()
                ->toArray();

            $byPlan = (clone $typeQuery)
                ->selectRaw('plans.name as plan, SUM(payment_transactions.amount) as amount')
                ->groupBy('plans.name')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => ['plan' => $row->plan, 'amount' => (float) $row->amount])
                ->values()
                ->toArray();

            return [
                'total_revenue' => $totalRevenue,
                'by_period' => $byPeriod,
                'by_service_type' => $byServiceType,
                'by_gateway' => $byGateway,
                'by_plan' => $byPlan,
            ];
        });
    }

    /**
     * Profit & Loss report: Revenue - Refunds - Gateway fees - Infrastructure cost.
     *
     * @return array{revenue: float, refunds: float, gateway_fees: float, infrastructure_cost: float, net_profit: float, margin_percentage: float}
     */
    public function profitLossReport(Carbon $startDate, Carbon $endDate): array
    {
        $cacheKey = "report.profit_loss.{$startDate->toDateString()}.{$endDate->toDateString()}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($startDate, $endDate): array {
            $stats = PaymentTransaction::query()
                ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->selectRaw("
                    COALESCE(SUM(CASE WHEN status = 'succeeded' THEN amount ELSE 0 END), 0) as revenue,
                    COALESCE(SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END), 0) as refunds,
                    COALESCE(SUM(CASE
                        WHEN status = 'succeeded' AND gateway = 'stripe' THEN (amount * 0.029) + 0.30
                        WHEN status = 'succeeded' AND gateway = 'paypal' THEN (amount * 0.0349) + 0.49
                        ELSE 0
                    END), 0) as gateway_fees
                ")
                ->first();

            $revenue = (float) $stats->revenue;
            $refunds = (float) $stats->refunds;
            $gatewayFees = round((float) $stats->gateway_fees, 2);

            // Infrastructure cost estimated at 40% of revenue (configurable)
            $infrastructureCostPercent = (float) config('billing.infrastructure_cost_percent', 40);
            $infrastructureCost = round($revenue * ($infrastructureCostPercent / 100), 2);

            $netProfit = round($revenue - $refunds - $gatewayFees - $infrastructureCost, 2);
            $marginPercentage = $revenue > 0 ? round(($netProfit / $revenue) * 100, 1) : 0;

            return [
                'revenue' => $revenue,
                'refunds' => $refunds,
                'gateway_fees' => $gatewayFees,
                'infrastructure_cost' => $infrastructureCost,
                'net_profit' => $netProfit,
                'margin_percentage' => $marginPercentage,
            ];
        });
    }

    /**
     * Tax report: paid invoices grouped by user billing country/region.
     *
     * @return array{total_tax: float, by_country: array<int, array{country: string, amount: float, invoice_count: int}>, by_region: array<int, array{region: string, country: string, amount: float}>}
     */
    public function taxReport(Carbon $startDate, Carbon $endDate): array
    {
        $cacheKey = "report.tax.{$startDate->toDateString()}.{$endDate->toDateString()}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($startDate, $endDate): array {
            $totalTax = (float) Invoice::query()
                ->where('status', 'paid')
                ->whereBetween('paid_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->where('tax', '>', 0)
                ->sum('tax');

            $byCountry = Invoice::query()
                ->where('invoices.status', 'paid')
                ->whereBetween('invoices.paid_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->where('invoices.tax', '>', 0)
                ->join('user_profiles', 'invoices.user_id', '=', 'user_profiles.user_id')
                ->selectRaw('COALESCE(user_profiles.billing_country, "Unknown") as country, SUM(invoices.tax) as amount, COUNT(invoices.id) as invoice_count')
                ->groupBy('country')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => [
                    'country' => $row->country,
                    'amount' => (float) $row->amount,
                    'invoice_count' => (int) $row->invoice_count,
                ])
                ->values()
                ->toArray();

            $byRegion = Invoice::query()
                ->where('invoices.status', 'paid')
                ->whereBetween('invoices.paid_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->where('invoices.tax', '>', 0)
                ->join('user_profiles', 'invoices.user_id', '=', 'user_profiles.user_id')
                ->selectRaw('COALESCE(user_profiles.billing_state, "Unknown") as region, COALESCE(user_profiles.billing_country, "Unknown") as country, SUM(invoices.tax) as amount')
                ->groupBy('region', 'country')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => [
                    'region' => $row->region,
                    'country' => $row->country,
                    'amount' => (float) $row->amount,
                ])
                ->values()
                ->toArray();

            return [
                'total_tax' => $totalTax,
                'by_country' => $byCountry,
                'by_region' => $byRegion,
            ];
        });
    }

    /**
     * Aging report: pending/overdue invoices grouped by age brackets.
     *
     * @return array{current: array{count: int, amount: float}, days_31_60: array{count: int, amount: float}, days_61_90: array{count: int, amount: float}, days_90_plus: array{count: int, amount: float}, total: array{count: int, amount: float}, invoices: array<int, array{id: int, invoice_number: string, customer: string, amount: float, due_date: string, days_overdue: int}>}
     */
    public function agingReport(): array
    {
        $cacheKey = 'report.aging';

        return Cache::remember($cacheKey, self::CACHE_TTL, function (): array {
            $invoices = Invoice::query()
                ->whereIn('status', ['pending', 'overdue'])
                ->with('user')
                ->get();

            $buckets = [
                'current' => ['count' => 0, 'amount' => 0.0],
                'days_31_60' => ['count' => 0, 'amount' => 0.0],
                'days_61_90' => ['count' => 0, 'amount' => 0.0],
                'days_90_plus' => ['count' => 0, 'amount' => 0.0],
            ];
            $total = ['count' => 0, 'amount' => 0.0];
            $invoiceDetails = [];

            foreach ($invoices as $invoice) {
                $dueDate = $invoice->due_date ?? $invoice->created_at;
                $daysOverdue = max(0, (int) now()->diffInDays($dueDate, false) * -1);
                $amount = (float) $invoice->total;

                $bucket = match (true) {
                    $daysOverdue <= 30 => 'current',
                    $daysOverdue <= 60 => 'days_31_60',
                    $daysOverdue <= 90 => 'days_61_90',
                    default => 'days_90_plus',
                };

                $buckets[$bucket]['count']++;
                $buckets[$bucket]['amount'] = round($buckets[$bucket]['amount'] + $amount, 2);
                $total['count']++;
                $total['amount'] = round($total['amount'] + $amount, 2);

                $invoiceDetails[] = [
                    'id' => $invoice->id,
                    'invoice_number' => $invoice->number,
                    'customer' => $invoice->user?->name ?? 'Unknown',
                    'amount' => $amount,
                    'due_date' => $dueDate->toDateString(),
                    'days_overdue' => $daysOverdue,
                ];
            }

            // Sort invoices by days overdue descending
            usort($invoiceDetails, fn ($a, $b) => $b['days_overdue'] <=> $a['days_overdue']);

            return [
                'current' => $buckets['current'],
                'days_31_60' => $buckets['days_31_60'],
                'days_61_90' => $buckets['days_61_90'],
                'days_90_plus' => $buckets['days_90_plus'],
                'total' => $total,
                'invoices' => $invoiceDetails,
            ];
        });
    }

    /**
     * Refund report: transactions with status=refunded grouped by gateway and month.
     *
     * @return array{total_refunds: float, refund_count: int, by_gateway: array<int, array{gateway: string, amount: float, count: int}>, by_month: array<int, array{month: string, amount: float, count: int}>}
     */
    public function refundReport(Carbon $startDate, Carbon $endDate): array
    {
        $cacheKey = "report.refund.{$startDate->toDateString()}.{$endDate->toDateString()}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($startDate, $endDate): array {
            $baseQuery = PaymentTransaction::query()
                ->where('status', 'refunded')
                ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()]);

            $totals = (clone $baseQuery)
                ->selectRaw('COALESCE(SUM(amount), 0) as total_refunds, COUNT(*) as refund_count')
                ->first();

            $byGateway = (clone $baseQuery)
                ->selectRaw('gateway, SUM(amount) as amount, COUNT(*) as count')
                ->groupBy('gateway')
                ->orderByDesc('amount')
                ->get()
                ->map(fn ($row) => [
                    'gateway' => $row->gateway,
                    'amount' => (float) $row->amount,
                    'count' => (int) $row->count,
                ])
                ->values()
                ->toArray();

            $byMonth = (clone $baseQuery)
                ->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as month, SUM(amount) as amount, COUNT(*) as count")
                ->groupBy('month')
                ->orderBy('month')
                ->get()
                ->map(fn ($row) => [
                    'month' => $row->month,
                    'amount' => (float) $row->amount,
                    'count' => (int) $row->count,
                ])
                ->values()
                ->toArray();

            return [
                'total_refunds' => (float) $totals->total_refunds,
                'refund_count' => (int) $totals->refund_count,
                'by_gateway' => $byGateway,
                'by_month' => $byMonth,
            ];
        });
    }

    /**
     * Subscription report: new/cancelled subs, churn rate, MRR start/end.
     *
     * @return array{new_subscriptions: int, cancelled_subscriptions: int, churn_rate: float, mrr_start: float, mrr_end: float, mrr_change: float, by_plan: array<int, array{plan: string, new: int, cancelled: int, active: int}>}
     */
    public function subscriptionReport(Carbon $startDate, Carbon $endDate): array
    {
        $cacheKey = "report.subscription.{$startDate->toDateString()}.{$endDate->toDateString()}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($startDate, $endDate): array {
            $newSubscriptions = Subscription::query()
                ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->count();

            $cancelledSubscriptions = Subscription::query()
                ->whereBetween('cancelled_at', [$startDate->startOfDay(), $endDate->endOfDay()])
                ->count();

            // Total at start of period
            $totalAtStart = Subscription::query()
                ->where('created_at', '<', $startDate->startOfDay())
                ->where(function ($query) use ($startDate): void {
                    $query->whereNull('cancelled_at')
                        ->orWhere('cancelled_at', '>', $startDate->startOfDay());
                })
                ->count();

            $churnRate = $totalAtStart > 0
                ? round(($cancelledSubscriptions / $totalAtStart) * 100, 1)
                : 0;

            $mrrStart = $this->calculateMrrAtDate($startDate);
            $mrrEnd = $this->calculateMrrAtDate($endDate);
            $mrrChange = round($mrrEnd - $mrrStart, 2);

            // Breakdown by plan
            $byPlan = DB::table('plans')
                ->leftJoin('subscriptions as new_subs', function ($join) use ($startDate, $endDate): void {
                    $join->on('plans.id', '=', 'new_subs.plan_id')
                        ->whereBetween('new_subs.created_at', [$startDate->startOfDay(), $endDate->endOfDay()]);
                })
                ->leftJoin('subscriptions as cancelled_subs', function ($join) use ($startDate, $endDate): void {
                    $join->on('plans.id', '=', 'cancelled_subs.plan_id')
                        ->whereBetween('cancelled_subs.cancelled_at', [$startDate->startOfDay(), $endDate->endOfDay()]);
                })
                ->leftJoin('subscriptions as active_subs', function ($join): void {
                    $join->on('plans.id', '=', 'active_subs.plan_id')
                        ->where('active_subs.stripe_status', 'active');
                })
                ->select(
                    'plans.name as plan',
                    DB::raw('COUNT(DISTINCT new_subs.id) as new_count'),
                    DB::raw('COUNT(DISTINCT cancelled_subs.id) as cancelled_count'),
                    DB::raw('COUNT(DISTINCT active_subs.id) as active_count'),
                )
                ->groupBy('plans.id', 'plans.name')
                ->having(DB::raw('COUNT(DISTINCT new_subs.id) + COUNT(DISTINCT cancelled_subs.id) + COUNT(DISTINCT active_subs.id)'), '>', 0)
                ->orderByDesc('active_count')
                ->get()
                ->map(fn ($row) => [
                    'plan' => $row->plan,
                    'new' => (int) $row->new_count,
                    'cancelled' => (int) $row->cancelled_count,
                    'active' => (int) $row->active_count,
                ])
                ->values()
                ->toArray();

            return [
                'new_subscriptions' => $newSubscriptions,
                'cancelled_subscriptions' => $cancelledSubscriptions,
                'churn_rate' => $churnRate,
                'mrr_start' => $mrrStart,
                'mrr_end' => $mrrEnd,
                'mrr_change' => $mrrChange,
                'by_plan' => $byPlan,
            ];
        });
    }

    /**
     * Calculate MRR at a specific date.
     */
    private function calculateMrrAtDate(Carbon $date): float
    {
        return (float) (Subscription::query()
            ->where('subscriptions.stripe_status', 'active')
            ->whereNotNull('subscriptions.plan_id')
            ->where('subscriptions.created_at', '<=', $date->endOfDay())
            ->where(function ($query) use ($date): void {
                $query->whereNull('subscriptions.cancelled_at')
                    ->orWhere('subscriptions.cancelled_at', '>', $date->endOfDay());
            })
            ->leftJoin('plan_prices', function ($join): void {
                $join->on('subscriptions.plan_id', '=', 'plan_prices.plan_id')
                    ->on('subscriptions.billing_cycle', '=', 'plan_prices.billing_cycle');
            })
            ->selectRaw('SUM(CASE
                WHEN subscriptions.recurring_amount IS NOT NULL THEN
                    CASE subscriptions.billing_cycle
                        WHEN "monthly" THEN subscriptions.recurring_amount
                        WHEN "quarterly" THEN subscriptions.recurring_amount / 3
                        WHEN "semi_annual" THEN subscriptions.recurring_amount / 6
                        WHEN "annual" THEN subscriptions.recurring_amount / 12
                        ELSE subscriptions.recurring_amount
                    END
                ELSE
                    CASE subscriptions.billing_cycle
                        WHEN "monthly" THEN plan_prices.price
                        WHEN "quarterly" THEN plan_prices.price / 3
                        WHEN "semi_annual" THEN plan_prices.price / 6
                        WHEN "annual" THEN plan_prices.price / 12
                        ELSE plan_prices.price
                    END
            END) as mrr')
            ->value('mrr') ?? 0);
    }
}
