<?php

namespace App\Services;

use App\Enums\ChargeBasis;
use App\Enums\PackagePricingMethod;
use App\Enums\PackageStatus;
use App\Models\Package;
use App\Models\PackageItem;
use Illuminate\Support\Str;

class PackageService
{
    public function __construct(
        private readonly PricingService $pricingService,
    ) {}

    public function duplicate(Package $package): Package
    {
        $duplicate = $package->replicate();
        $duplicate->code = $this->generateUniqueCode($package->code);
        $duplicate->name = $package->name.' (Copy)';
        $duplicate->status = PackageStatus::Draft;
        $duplicate->push();

        foreach ($package->items as $item) {
            $newItem = $item->replicate();
            $newItem->package_id = $duplicate->id;
            $newItem->save();
        }

        return $duplicate->load('items.product');
    }

    /**
     * @return array{
     *     lines: list<array<string, mixed>>,
     *     line_totals: list<array{base: float, discount: float, taxable: float, tax: float, total: float}>,
     *     document_totals: array<string, float>
     * }
     */
    public function estimate(
        Package $package,
        int $guestCount = 1,
        int $billableDays = 1,
        float $billableHours = 1,
        array $documentOptions = [],
    ): array {
        $lines = $this->buildEstimateLines($package, $guestCount, $billableDays, $billableHours);

        $lineTotals = array_map(
            fn (array $line): array => $this->pricingService->calculateLineAmount($line),
            $lines,
        );

        $documentTotals = $this->pricingService->calculateDocumentTotals($lines, $documentOptions);

        return [
            'lines' => $lines,
            'line_totals' => $lineTotals,
            'document_totals' => $documentTotals,
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function buildEstimateLines(
        Package $package,
        int $guestCount,
        int $billableDays,
        float $billableHours,
    ): array {
        if ($package->pricing_method === PackagePricingMethod::Fixed) {
            return [[
                'description' => $package->name,
                'package_id' => $package->id,
                'charge_basis' => ChargeBasis::FixedPackage->value,
                'fixed_amount' => $package->fixed_price ?? 0,
                'unit_price' => $package->fixed_price ?? 0,
                'quantity' => 1,
                'guest_count' => $guestCount,
                'billable_days' => $billableDays,
                'billable_hours' => $billableHours,
                'tax_rate' => 0,
                'discount_amount' => 0,
            ]];
        }

        $package->loadMissing('items.product');

        return $package->items
            ->map(fn (PackageItem $item): array => $this->itemToLine($item, $guestCount, $billableDays, $billableHours))
            ->values()
            ->all();
    }

    /**
     * @return array<string, mixed>
     */
    public function itemToLine(
        PackageItem $item,
        int $guestCount,
        int $billableDays,
        float $billableHours,
    ): array {
        $product = $item->product;

        if ($item->is_complimentary) {
            return [
                'description' => $item->description_override ?: ($product?->name ?? 'Complimentary item'),
                'product_id' => $product?->id,
                'package_id' => $item->package_id,
                'charge_basis' => ChargeBasis::Complimentary->value,
                'unit_price' => 0,
                'quantity' => $item->default_quantity,
                'guest_count' => $guestCount,
                'billable_days' => $billableDays,
                'billable_hours' => $billableHours,
                'tax_rate' => 0,
                'discount_amount' => 0,
            ];
        }

        $chargeBasis = $item->charge_basis_override ?? $product->default_charge_basis;

        return [
            'description' => $item->description_override ?: ($product?->name ?? 'Package item'),
            'product_id' => $product?->id,
            'package_id' => $item->package_id,
            'charge_basis' => $chargeBasis instanceof ChargeBasis ? $chargeBasis->value : $chargeBasis,
            'unit_price' => $item->price_override ?? $product->default_price,
            'quantity' => $item->default_quantity,
            'guest_count' => $guestCount,
            'billable_days' => $billableDays,
            'billable_hours' => $billableHours,
            'tax_rate' => $product->default_tax_rate ?? 0,
            'discount_amount' => 0,
        ];
    }

    private function generateUniqueCode(string $baseCode): string
    {
        $candidate = $baseCode.'-COPY';

        while (Package::query()->where('code', $candidate)->exists()) {
            $candidate = $baseCode.'-COPY-'.Str::upper(Str::random(4));
        }

        return $candidate;
    }
}
