<?php

namespace App\Services;

use App\Enums\ChargeStatus;
use App\Models\BillingPeriod;
use App\Models\Line;
use App\Models\LineBillingCharge;
use Carbon\Carbon;
use Illuminate\Support\Collection;

class BillingService
{
    public function ensurePeriod(int $year, int $month): BillingPeriod
    {
        return BillingPeriod::findOrCreatePeriod($year, $month);
    }

    public function generateRange(int $fromYear, int $fromMonth, int $toYear, int $toMonth): Collection
    {
        $start = Carbon::createFromDate($fromYear, $fromMonth, 1)->startOfMonth();
        $end = Carbon::createFromDate($toYear, $toMonth, 1)->startOfMonth();
        $periods = collect();

        while ($start->lte($end)) {
            $periods->push($this->ensurePeriod((int) $start->year, (int) $start->month));
            $start->addMonth();
        }

        return $periods;
    }

    public function priceForPeriod(Line $line, BillingPeriod $period): string
    {
        $plan = $line->currentPlan;
        if (! $plan) {
            $assignment = $line->linePlans()->where('status', 'active')->latest('id')->first();
            $plan = $assignment?->plan;
        }

        if (! $plan) {
            return '0.00';
        }

        $assignment = $line->linePlans()
            ->where('plan_id', $plan->id)
            ->whereDate('start_date', '<=', $period->end_date)
            ->where(function ($q) use ($period) {
                $q->whereNull('end_date')->orWhereDate('end_date', '>=', $period->start_date);
            })
            ->latest('id')
            ->first();

        if ($assignment && $period->start_date->lt($assignment->start_date) === false) {
            // Historical invoice prices stay on charges/invoices; assignment price is a fallback
            // when no later plan price history applies.
        }

        return $plan->priceAt($period->start_date);
    }

    public function snapshot(Line $line): array
    {
        $line->load(['customer.mainAccount', 'customer.subAccount', 'currentPlan', 'mainAccount', 'subAccount', 'telecomCompany', 'charges.billingPeriod', 'invoices', 'payments', 'sales']);

        $paidCharges = $line->charges->where('status', ChargeStatus::Paid);
        $year = $this->subscriptionYear($line);
        $paidPeriods = collect($year['paid']);
        $unpaid = $year['remaining'];
        $outstanding = '0.00';
        foreach ($unpaid as $row) {
            $outstanding = bcadd($outstanding, (string) $row['amount'], 2);
        }

        $lastPaid = $paidCharges->sortByDesc(fn ($c) => sprintf('%04d%02d', $c->billingPeriod?->year, $c->billingPeriod?->month))->first();
        $paidThrough = $paidPeriods->last()['period'] ?? $lastPaid?->billingPeriod;

        $lastInvoice = $line->invoices()->latest('date')->latest('id')->first();
        $lastPayment = $line->payments()->where('status', 'completed')->latest('date')->latest('id')->first();

        return [
            'line' => $line,
            'customer' => $line->customer,
            'plan' => $line->currentPlan,
            'plan_price' => $line->currentPlan?->price,
            'last_invoice' => $lastInvoice,
            'last_payment' => $lastPayment,
            'paid_through' => $paidThrough,
            'paid_periods' => $paidPeriods->all(),
            'unpaid_periods' => $unpaid,
            'outstanding' => $outstanding,
            'year_start' => $year['start'],
            'year_end' => $year['end'],
            'year_number' => $year['year_number'] ?? 1,
            'new_year_opened' => ($year['year_number'] ?? 1) > 1 && count($year['paid']) === 0,
        ];
    }

    public function subscriptionStart(Line $line): ?Carbon
    {
        $start = $line->activation_date ?: $line->sale_date;

        return $start ? Carbon::parse($start)->startOfMonth() : null;
    }

    public function firstMonthCoveredBySale(Line $line): bool
    {
        $sale = $line->relationLoaded('sales')
            ? $line->sales->sortByDesc('id')->first()
            : $line->sales()->latest('id')->first();

        return $sale && bccomp((string) ($sale->plan_price ?? '0'), '0.00', 2) === 1;
    }

    public function subscriptionYear(Line $line): array
    {
        $origin = $this->subscriptionStart($line);
        $empty = ['start' => $origin, 'end' => $origin?->copy()->addMonths(11), 'paid' => [], 'remaining' => [], 'year_number' => 1];

        if (! $origin || (! $line->current_plan_id && ! $line->linePlans()->exists())) {
            return $empty;
        }

        $index = $this->yearIndexFor($origin);
        $yearStart = $origin->copy()->addMonths($index * 12);
        $built = $this->buildYear($line, $origin, $yearStart, $index + 1);

        if ($built['remaining'] === []) {
            $index++;
            $yearStart = $origin->copy()->addMonths($index * 12);
            $built = $this->buildYear($line, $origin, $yearStart, $index + 1);
        }

        return $built;
    }

    public function yearIndexFor(Carbon $origin, ?Carbon $asOf = null): int
    {
        $asOf = ($asOf ?? now())->copy()->startOfMonth();
        if ($asOf->lt($origin)) {
            return 0;
        }

        return intdiv($origin->diffInMonths($asOf), 12);
    }

    protected function buildYear(Line $line, Carbon $origin, Carbon $yearStart, int $yearNumber): array
    {
        $end = $yearStart->copy()->addMonths(11);
        $paid = [];
        $remaining = [];
        $cursor = $yearStart->copy();
        $saleCoversFirst = $this->firstMonthCoveredBySale($line);

        while ($cursor->lte($end)) {
            $period = $this->ensurePeriod((int) $cursor->year, (int) $cursor->month);
            $charge = $line->charges->firstWhere('billing_period_id', $period->id)
                ?? LineBillingCharge::query()->where('line_id', $line->id)->where('billing_period_id', $period->id)->first();
            $isOriginMonth = $cursor->equalTo($origin);
            $paidBySale = $isOriginMonth && $saleCoversFirst && (! $charge || $charge->status !== ChargeStatus::Unpaid);

            if ($charge && $charge->status === ChargeStatus::Paid) {
                $paid[] = ['period' => $period, 'amount' => (string) $charge->amount, 'via_sale' => false];
            } elseif ($paidBySale) {
                $paid[] = ['period' => $period, 'amount' => (string) ($line->sales()->latest('id')->value('plan_price') ?? '0'), 'via_sale' => true];
            } elseif (! $charge || $charge->status === ChargeStatus::Unpaid) {
                $remaining[] = [
                    'period' => $period,
                    'amount' => $charge ? (string) $charge->amount : $this->priceForPeriod($line, $period),
                    'charge_id' => $charge?->id,
                ];
            }

            $cursor->addMonth();
        }

        return ['start' => $yearStart, 'end' => $end, 'paid' => $paid, 'remaining' => $remaining, 'year_number' => $yearNumber];
    }

    public function outstandingPeriods(Line $line): array
    {
        return $this->subscriptionYear($line)['remaining'];
    }

    public function overdueQuery(array $filters)
    {
        $year = (int) ($filters['year'] ?? now()->year);
        $month = (int) ($filters['month'] ?? now()->month);
        $period = $this->ensurePeriod($year, $month);

        $query = Line::query()
            ->with(['customer', 'currentPlan', 'mainAccount', 'subAccount', 'telecomCompany', 'charges.billingPeriod'])
            ->whereNotNull('customer_id')
            ->whereIn('status', ['sold', 'active', 'suspended'])
            ->where(function ($q) use ($period) {
                $q->whereDate('activation_date', '<=', $period->end_date)
                    ->orWhere(function ($inner) use ($period) {
                        $inner->whereNull('activation_date')->whereDate('sale_date', '<=', $period->end_date);
                    });
            });

        if (! empty($filters['telecom_company_id'])) {
            $query->where('telecom_company_id', $filters['telecom_company_id']);
        }
        if (! empty($filters['plan_id'])) {
            $query->where('current_plan_id', $filters['plan_id']);
        }
        if (! empty($filters['main_account_id'])) {
            $query->where('main_account_id', $filters['main_account_id']);
        }
        if (! empty($filters['sub_account_id'])) {
            $query->where('sub_account_id', $filters['sub_account_id']);
        }
        if (! empty($filters['customer_id'])) {
            $query->where('customer_id', $filters['customer_id']);
        }
        if (! empty($filters['line_id'])) {
            $query->where('id', $filters['line_id']);
        }
        if (! empty($filters['q'])) {
            $query->search($filters['q']);
        }

        $query->where(function ($q) use ($period) {
            $q->whereDoesntHave('charges', function ($c) use ($period) {
                $c->where('billing_period_id', $period->id)->where('status', ChargeStatus::Paid->value);
            });
        });

        $query->where(function ($q) use ($period) {
            $q->where(function ($notFirstMonth) use ($period) {
                $notFirstMonth->whereNull('activation_date')
                    ->orWhereMonth('activation_date', '!=', $period->month)
                    ->orWhereYear('activation_date', '!=', $period->year);
            })->orWhereDoesntHave('sales', function ($s) {
                $s->where('plan_price', '>', 0);
            });
        });

        return [$query, $period];
    }
}
