<?php
declare(strict_types=1);

namespace App\Modules\Rentals\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Rentals\Models\RentalContract;
use App\Modules\Rentals\Models\RentalBooking;
use App\Modules\Rules\Services\RuleEngine;

final class RentalContractService
{
    /**
     * Create a new rental contract.
     *
     * Generates the contract number, calculates totals (subtotal = units * rate),
     * applies bulk discount if eligible, calculates escalation and deposit.
     */
    public static function createContract(array $data): object
    {
        $contractNumber = RentalContract::generateNumber();

        $totalUnits = (int) ($data['total_units'] ?? 0);
        $unitRate   = (float) ($data['unit_rate'] ?? 0);
        $subtotal   = $totalUnits * $unitRate;

        // Calculate discount
        $startDate = $data['start_date'] ?? '';
        $endDate   = $data['end_date'] ?? '';
        $months    = 0;
        if ($startDate && $endDate) {
            $start  = new \DateTimeImmutable($startDate);
            $end    = new \DateTimeImmutable($endDate);
            $diff   = $start->diff($end);
            $months = ($diff->y * 12) + $diff->m + ($diff->d > 0 ? 1 : 0);
        }

        $discountPercentage  = self::calculateBulkDiscount($totalUnits, $months);
        $discountAmount      = round($subtotal * ($discountPercentage / 100), 2);
        $baseNetTotal        = $subtotal - $discountAmount;   // before escalation

        // VAT — default from RENTAL_VAT_PCT rule (1%), overridable per contract
        $defaultVat    = (float) (RuleEngine::getValue('RENTAL_VAT_PCT') ?? 1.00);
        $vatPercentage = (float) ($data['vat_percentage'] ?? $defaultVat);

        // ── Escalation ──────────────────────────────────────────────
        $escalationType  = $data['escalation_type']  ?? 'none';
        $escalationRate  = (float) ($data['escalation_rate'] ?? 0.00);
        $tiersJson       = $data['escalation_tiers_json'] ?? null;
        $tiersArray      = [];
        if ($tiersJson && is_string($tiersJson)) {
            $decoded = json_decode($tiersJson, true);
            $tiersArray = is_array($decoded) ? $decoded : [];
        }

        // base_monthly_year1 = net total / months (year-1 unescalated monthly)
        $baseMonthlyYear1 = $months > 0 ? ($baseNetTotal / $months) : $baseNetTotal;

        if ($escalationType !== 'none' && $months > 0) {
            $totalAmount = round(self::computeEscalatedTotal($baseMonthlyYear1, $months, $escalationType, $escalationRate, $tiersArray), 2);
        } else {
            $totalAmount = $baseNetTotal;
        }

        // ── Utilities ────────────────────────────────────────────────
        $utilitiesMode        = $data['utilities_mode']         ?? 'rent_pct';
        $utilitiesRentPct     = (float) ($data['utilities_rent_pct']     ?? 0.00);
        $utilitiesFacilityPct = (float) ($data['utilities_facility_pct'] ?? 0.00);
        $facilityMonthlyCost  = isset($data['facility_monthly_cost']) && $data['facility_monthly_cost'] !== ''
            ? (float) $data['facility_monthly_cost']
            : null;

        // legacy utilities_percentage for backward compat with old invoice calc
        $utilitiesPercentage = match ($utilitiesMode) {
            'rent_pct', 'both' => $utilitiesRentPct,
            default            => 0.00,
        };

        // utilities_amount = total utilities across the whole contract
        $monthlyUtilFromRent     = 0.00;
        $monthlyUtilFromFacility = 0.00;

        if (in_array($utilitiesMode, ['rent_pct', 'both'], true)) {
            $monthlyUtilFromRent = $baseMonthlyYear1 * ($utilitiesRentPct / 100);
        }
        if (in_array($utilitiesMode, ['facility_cost_pct', 'both'], true) && $facilityMonthlyCost !== null) {
            $monthlyUtilFromFacility = $facilityMonthlyCost * ($utilitiesFacilityPct / 100);
        }

        $utilitiesAmount = round(($monthlyUtilFromRent + $monthlyUtilFromFacility) * max(1, $months), 2);

        // ── VAT & Grand Total ─────────────────────────────────────────
        $vatAmount  = round(($totalAmount + $utilitiesAmount) * ($vatPercentage / 100), 2);
        $grandTotal = $totalAmount + $utilitiesAmount + $vatAmount;

        // ── Late fee ──────────────────────────────────────────────────
        $lateFeeType     = $data['late_fee_type']      ?? 'none';
        $lateFeeRate     = (float) ($data['late_fee_rate']     ?? 0.00);
        $lateFeeBankRate = (float) ($data['late_fee_bank_rate'] ?? 0.00);

        // ── Payment & Terms ───────────────────────────────────────────
        $paymentDueDay          = (int) ($data['payment_due_day']          ?? 5);
        $gracePeriodMonths      = (int) ($data['grace_period_months']      ?? 0);
        $earlyTerminationMonths = (int) ($data['early_termination_months'] ?? 0);

        // ── Deposit — % of grand total ────────────────────────────────
        $depositPercentage = (float) ($data['deposit_percentage'] ?? 0);
        $depositAmount     = round($grandTotal * ($depositPercentage / 100), 2);

        $contract = RentalContract::create([
            'contract_number'            => $contractNumber,
            'entity_id'                  => (int) $data['entity_id'],
            'facility_id'                => (int) $data['facility_id'],
            'activity_type'              => $data['activity_type'] ?? null,
            'start_date'                 => $startDate,
            'end_date'                   => $endDate,
            'total_units'                => $totalUnits,
            'unit_rate'                  => $unitRate,
            'time_tier'                  => $data['time_tier'] ?? 'AM',
            'subtotal'                   => $subtotal,
            'discount_percentage'        => $discountPercentage,
            'discount_amount'            => $discountAmount,
            'total_amount'               => $totalAmount,
            'vat_percentage'             => $vatPercentage,
            'vat_amount'                 => $vatAmount,
            'utilities_percentage'       => $utilitiesPercentage,
            'utilities_amount'           => $utilitiesAmount,
            'grand_total'                => $grandTotal,
            'late_fee_type'              => $lateFeeType,
            'late_fee_rate'              => $lateFeeRate,
            'escalation_type'            => $escalationType,
            'escalation_rate'            => $escalationRate,
            'escalation_tiers_json'      => ($tiersJson !== '' && $tiersJson !== null) ? $tiersJson : null,
            'utilities_mode'             => $utilitiesMode,
            'utilities_rent_pct'         => $utilitiesRentPct,
            'utilities_facility_pct'     => $utilitiesFacilityPct,
            'facility_monthly_cost'      => $facilityMonthlyCost,
            'payment_due_day'            => $paymentDueDay,
            'late_fee_bank_rate'         => $lateFeeBankRate,
            'grace_period_months'        => $gracePeriodMonths,
            'early_termination_months'   => $earlyTerminationMonths,
            'deposit_percentage'         => $depositPercentage,
            'deposit_amount'             => $depositAmount,
            'deposit_status'             => 'pending',
            'technical_report_submitted' => 0,
            'status'                     => 'draft',
            'terms_json'                 => $data['terms_json'] ?? null,
            'notes'                      => $data['notes'] ?? null,
        ]);

        EventBus::dispatch('rental.contract_created', [
            'contract_id' => (int) $contract->id,
            'entity_id'   => (int) $data['entity_id'],
        ]);

        Logger::info('Rental contract created', ['contract_id' => $contract->id, 'number' => $contractNumber]);

        return $contract;
    }

    /**
     * Compute the sum of all escalated monthly payments across the contract duration.
     *
     * For 'flat'  : month m (0-indexed) → base × (1 + rate/100)^floor(m/12)
     * For 'tiered': same but rate for each year comes from the tier covering that year
     *
     * Tiers format: [{months: N, annual_rate: R}, ...] — each tier covers N months (cumulative).
     */
    private static function computeEscalatedTotal(
        float $baseMonthlyYear1,
        int   $totalMonths,
        string $escType,
        float  $escRate,
        array  $tiers
    ): float {
        $total = 0.0;

        for ($m = 0; $m < $totalMonths; $m++) {
            $yearIndex = (int) floor($m / 12);   // 0 = year 1, 1 = year 2 …

            $multiplier = 1.0;
            if ($escType === 'flat') {
                $multiplier = (1 + $escRate / 100) ** $yearIndex;
            } elseif ($escType === 'tiered') {
                // Each tier covers `months` months from the start; derive per-year rate
                $cumulativeMonths = 0;
                $appliedRate      = 0.0;
                $tierForYear      = 0.0;

                // Map yearIndex → rate from tiers
                // year 0 = months 0-11, year 1 = months 12-23 …
                $yearStartMonth = $yearIndex * 12;
                foreach ($tiers as $tier) {
                    $tierMonths = (int) ($tier['months'] ?? 12);
                    $tierRate   = (float) ($tier['annual_rate'] ?? 0.00);
                    $cumulativeMonths += $tierMonths;
                    // if year-start-month falls within this tier's cumulative range, use its rate
                    if ($yearStartMonth < $cumulativeMonths) {
                        $tierForYear = $tierRate;
                        break;
                    }
                    $appliedRate = $tierRate;
                }

                // Build multiplier: for each year Y from 1 to yearIndex, compound its tier rate
                $compound = 1.0;
                $cumM = 0;
                for ($y = 0; $y < $yearIndex; $y++) {
                    $yStartMonth = $y * 12;
                    $yRate = 0.0;
                    $cumMInner = 0;
                    foreach ($tiers as $tier) {
                        $tierMonths = (int) ($tier['months'] ?? 12);
                        $tierRate   = (float) ($tier['annual_rate'] ?? 0.00);
                        $cumMInner += $tierMonths;
                        if ($yStartMonth < $cumMInner) {
                            $yRate = $tierRate;
                            break;
                        }
                    }
                    $compound *= (1 + $yRate / 100);
                }
                $multiplier = $compound;
            }

            $total += $baseMonthlyYear1 * $multiplier;
        }

        return round($total, 2);
    }

    /**
     * Activate a rental contract.
     */
    public static function activateContract(int $contractId): void
    {
        $contract = RentalContract::find($contractId);
        if (!$contract) {
            throw new \RuntimeException('Contract not found');
        }

        $contract->update(['status' => 'active']);

        EventBus::dispatch('rental.contract_activated', [
            'contract_id' => $contractId,
        ]);

        Logger::info('Rental contract activated', ['contract_id' => $contractId]);
    }

    /**
     * Generate booking slots from a schedule array.
     *
     * @param int   $contractId
     * @param array $schedule Array of [{date, start_time, end_time}]
     */
    public static function generateBookingSlots(int $contractId, array $schedule): void
    {
        $contract = RentalContract::find($contractId);
        if (!$contract) {
            throw new \RuntimeException('Contract not found');
        }

        $facilityId = (int) $contract->facility_id;

        foreach ($schedule as $slot) {
            RentalBooking::create([
                'contract_id'  => $contractId,
                'facility_id'  => $facilityId,
                'booking_date' => $slot['date'],
                'start_time'   => $slot['start_time'],
                'end_time'     => $slot['end_time'],
                'status'       => 'scheduled',
            ]);
        }

        Logger::info('Rental booking slots generated', [
            'contract_id' => $contractId,
            'count'       => count($schedule),
        ]);
    }

    /**
     * Calculate bulk discount based on total units and contract duration.
     *
     * Default: if units >= 24 AND months >= 2, return 15%.
     * Values are read from business_rules: RENTAL_BULK_MIN_UNITS,
     * RENTAL_BULK_MIN_MONTHS, RENTAL_BULK_DISCOUNT_PCT.
     */
    public static function calculateBulkDiscount(int $totalUnits, int $months): float
    {
        $minUnits    = (int) (RuleEngine::getValue('RENTAL_BULK_MIN_UNITS') ?? 24);
        $minMonths   = (int) (RuleEngine::getValue('RENTAL_BULK_MIN_MONTHS') ?? 2);
        $discountPct = (float) (RuleEngine::getValue('RENTAL_BULK_DISCOUNT_PCT') ?? 15.00);

        if ($totalUnits >= $minUnits && $months >= $minMonths) {
            return $discountPct;
        }

        return 0.00;
    }
}
