<?php

namespace App\Repositories;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Mtc\MercuryDataModels\DealBuilder\AddOn;
use Mtc\MercuryDataModels\DealBuilder\AddOnCondition;
use Mtc\MercuryDataModels\DealBuilder\Deal;
use Mtc\MercuryDataModels\DealBuilder\DealPaymentType;
use Mtc\MercuryDataModels\DealBuilder\Status;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceRequestData;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceResult;
use Mtc\MercuryDataModels\Finance\FinanceServiceHelper;
use Mtc\Checkout\InvoiceFactory;
use Mtc\MercuryDataModels\Customer;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleValuation;
use App\Facades\Settings;
use Illuminate\Support\Facades\App;

class DealBuilderRepository
{
    public const DEAL_SESSION_KEY = 'current-deal-id';

    public function getOrCreate(?int $deal_id, string $vehicle_slug, bool $force_new = false): Deal
    {
        /** @var Deal $deal */
        $deal = Deal::query()->find($deal_id);
        $vehicle = Vehicle::query()->active()->where('slug', $vehicle_slug)->firstOrFail();

        // Deal exists but already submitted - clear session and start fresh
        if ($deal && $deal->status_id !== 0) {
            Session::forget(self::DEAL_SESSION_KEY);
            $deal = null;
        }

        // Deal exists but for a different vehicle
        if ($deal && $vehicle->id !== $deal->vehicle_id) {
            if (!$force_new) {
                throw ValidationException::withMessages(['vehicle_id' => __('deals.exists_for_different_vehicle')]);
            } else {
                $deal = null;
            }
        }

        if (!$deal) {
            $defaults = $this->getDefaultFinanceSettings($vehicle);

            $deal = Deal::query()->create([
                'vehicle_id' => $vehicle->id,
                'status_id' => 0,
                'term' => $defaults['term'],
                'annual_mileage' => $defaults['annual_mileage'],
                'customer_deposit' => $defaults['customer_deposit'],
                'credit_rating' => $defaults['credit_rating'],
                'total_amount' => $vehicle->price,
                'deposit_amount' => $defaults['customer_deposit'],
            ]);
            Session::put(self::DEAL_SESSION_KEY, $deal->id);
        }

        return $deal;
    }

    public function getDefaultFinanceSettings(Vehicle $vehicle): array
    {
        $vehiclePrice = $vehicle->price ?? 0;
        $depositPercentage = (int) Settings::get('finance-deposit_percentage', 10);
        $customerDeposit = round($vehiclePrice * ($depositPercentage / 100), 2);

        return [
            'term' => (int) Settings::get('finance-term', 60),
            'annual_mileage' => (int) Settings::get('finance-annual_mileage', 10000),
            'deposit_percentage' => $depositPercentage,
            'customer_deposit' => $customerDeposit,
            'credit_rating' => Settings::get('finance-credit_rating', 'excellent'),
        ];
    }

    /**
     * @param int|null $deal_id
     * @return Deal|Model
     */
    public function get(?int $deal_id): Deal
    {
        return Deal::query()->findOrFail($deal_id);
    }

    public function addAddOn(Deal $deal, int $addOnId, string $paymentTerms): void
    {
        $addOn = AddOn::query()->find($addOnId);
        $deal->addOns()->updateOrCreate(
            ['add_on_id' => $addOnId],
            [
                'price' => $addOn->price,
                'payment_term' => $paymentTerms
            ]
        );
        $this->updateDealTotals($deal);
        if ($this->shouldReloadQuotes($deal)) {
            $this->reloadQuotes($deal);
        }
    }

    public function removeAddOn(Deal $deal, int $addOnId): void
    {
        $deal->addOns()
            ->where('add_on_id', $addOnId)
            ->delete();
        $this->updateDealTotals($deal);
        if ($this->shouldReloadQuotes($deal)) {
            $this->reloadQuotes($deal);
        }
    }

    public function addPartExchange(Deal $deal, array $input): void
    {
        // Check if multiple part-exchanges are allowed
        $allowMultiple = Settings::get('deal-builder-allow-multiple-part-exchange', false);
        if (!$allowMultiple && $deal->partExchange()->exists()) {
            throw ValidationException::withMessages([
                'part_exchange' => __('deal_builder.part_exchange_limit_reached'),
            ]);
        }

        $valuation = VehicleValuation::query()->find($input['valuation_id']);

        $deal->partExchange()->create([
            'provider' => $valuation->provider,
            'registration' => $input['registration'],
            'mileage' => $input['mileage'],
            'vehicle_type' => $valuation->vehicle_type,
            'make' => $valuation->make,
            'model' => $valuation->model,
            'derivative' => $valuation->derivative,
            'fuel_type' => $valuation->fuel_type,
            'engine_size' => $valuation->engine_size,
            'body_type' => $valuation->body_type,
            'transmission' => $valuation->transmission,
            'date_of_registration' => $valuation->date_of_registration,
            'valuation_made_at' => $valuation->valuation_made_at,
            'retail_price' => $valuation->retail_price,
            'average_price' => $valuation->average_price,
            'clean_price' => $valuation->clean_price,
            'below_price' => $valuation->below_price,
            'outstanding_finance' => $input['outstanding_finance'],
            'cashback_amount' => $input['cashback_amount'],
        ]);

        // Store email on the deal for early customer capture
        if (!empty($input['email'])) {
            $deal->update(['email' => $input['email']]);
        }

        $this->updateDealTotals($deal);
        if ($this->shouldReloadQuotes($deal)) {
            $this->reloadQuotes($deal);
        }
    }

    public function removePartExchange(Deal $deal, int $partExchangeId): void
    {
        $deal->partExchange()->where('id', $partExchangeId)->delete();

        $this->updateDealTotals($deal);
        if ($this->shouldReloadQuotes($deal)) {
            $this->reloadQuotes($deal);
        }
    }

    public function updatePartExchange(Deal $deal, array $input): void
    {
        $deal->partExchange()->where('id', $input['id'])->update($input);

        $this->updateDealTotals($deal);
        if ($this->shouldReloadQuotes($deal)) {
            $this->reloadQuotes($deal);
        }
    }

    public function setFinanceTerms(Deal $deal, array $input): void
    {
        $deal->update($input);
        $this->updateDealTotals($deal);
        $this->reloadQuotes($deal);
    }

    public function selectFinanceQuote(Deal $deal, int $quote_id): void
    {
        $deal->finance()
            ->where('id', '!=', $quote_id)
            ->update(['selected' => false]);

        $deal->finance()
            ->where('id', $quote_id)
            ->update(['selected' => true]);
    }

    public function setPaymentType(Deal $deal, string $payment_type): void
    {
        $payable_amount = match ($payment_type) {
            'reservation' => $deal->vehicle->getReservationAmount(),
            'deposit' => $deal->deposit_amount ?? 0,
            'full_payment' => $deal->total_amount ?? 0,
            default => 0,
        };

        $deal->update([
            'payment_type' => $payment_type,
            'payable_amount' => $payable_amount,
        ]);
    }

    public function submitDeal(Deal $deal, array $input): void
    {
        $initialStatus = Status::query()->where('is_initial', 1)->first();

        $deal->update([
            'first_name' => $input['first_name'],
            'last_name' => $input['last_name'],
            'email' => $input['email'],
            'contact_number' => $input['contact_number'],
            'status_id' => $initialStatus?->id,
        ]);

        if ($deal->wasChanged('status_id') && $initialStatus) {
            $deal->history()->create([
                'status_id' => $initialStatus->id,
                'customer_id' => $deal->customer_id,
            ]);
        }

        Session::forget(self::DEAL_SESSION_KEY);
    }

    public function initiatePayment(Deal $deal, array $input): array
    {
        // Find or create customer by email
        $customer = Customer::query()->firstOrCreate(
            ['email' => $input['email']],
            [
                'first_name' => $input['first_name'],
                'last_name' => $input['last_name'],
                'phone_number' => $input['contact_number'],
            ]
        );

        // Attach customer to deal and store contact details
        $deal->update([
            'customer_id' => $customer->id,
            'first_name' => $input['first_name'],
            'last_name' => $input['last_name'],
            'email' => $input['email'],
            'contact_number' => $input['contact_number'],
        ]);

        // Refresh deal to get customer relationship
        $deal->refresh();

        // Create invoice from deal
        $invoice = (new InvoiceFactory())->create($deal);

        // Update invoice with email
        $invoice->getModel()->update(['email' => $input['email']]);

        // Get payment config
        $config = $this->getPaymentConfig($deal, $invoice);

        return [
            'invoice_id' => $invoice->getId(),
            'config' => $config,
        ];
    }

    protected function getPaymentConfig(Deal $deal, $invoice): array
    {
        return [
            'provider' => 'stripe',
            'invoice_id' => $invoice->getId(),
            'amount' => $invoice->getOutstandingAmount() * 100,
            'country' => Settings::get('app-details-country'),
            'currency' => $invoice->getCurrency(),
            'item_name' => 'Payment for ' . $deal->vehicle->title,
            'stripe_public_key' => App::make(config('stripe.config'))->publicKey(),
            'customer' => null,
        ];
    }

    public function addOnsForVehicle(Vehicle $vehicle): Collection
    {
        return AddOn::query()
            ->where('active', 1)
            ->with('conditions')
            ->get()
            ->filter(fn (AddOn $addOn) => $this->isAddOnApplicableToVehicle($addOn, $vehicle));
    }

    private function shouldReloadQuotes(Deal $deal): bool
    {
        if ($deal->finance()->doesntExist()) {
            return false;
        }

        return $deal->wasChanged([
            'price',
            'annual_mileage',
            'term',
            'credit_rating',
            'customer_deposit',
            'deposit_amount',
            'total_amount',
            'part_ex_contribution',
        ]);
    }

    private function reloadQuotes(Deal $deal): void
    {
        $provider = FinanceServiceHelper::initializeForSite();
        $field = $provider->dealerIdDataField();
        // TODO: need to make sure we are not finding just one quote per type, but all
        $quotes = $provider->calculate(new FinanceRequestData(
            uuid: $deal->reference,
            registration_number: $deal->vehicle->registration_number,
            cap_id: $deal->vehicle->cap_id,
            dealer_id: $deal->vehicle->dealership->location_finance ?? '',
            engine_size: $deal->vehicle->engine_size_cc,
            condition: $deal->vehicle->condition,
            registration_date: $deal->vehicle->first_registration_date,
            term: $deal->term,
            mileage: $deal->vehicle->odometer_mi,
            annual_mileage: $deal->annual_mileage,
            price: $deal->vehicle->price,
            deposit: $deal->deposit_amount,
            credit_rating: $deal->credit_rating,
            clientKey: $deal->vehicle->dealership?->data[$field] ?? null,
        ));

        $deal->finance()->delete();
        $quotes->each(fn(FinanceResult $quote) => $deal->finance()->create([
            'provider' => FinanceServiceHelper::getProvider(),
            'type' => $quote->finance_type,
            'lender_name' => $quote->lender_name,
            'lender_logo' => $quote->lender_logo,
            'quote_reference' => $quote->quote_reference,
            'quote_valid_until' => $quote->quote_valid_until,
            'apply_url' => $quote->apply_url,
            'retailer_apply_url' => $quote->retailer_apply_url,
            'print_url' => $quote->print_url,
            'credit_check_url' => $quote->credit_check_url,
            'annual_mileage' => $quote->annual_mileage,
            'first_monthly' => $quote->first_payment,
            'monthly_payment' => $quote->monthly_price,
            'final_payment' => $quote->final_payment,
            'full_price' => $quote->payable_amount,
            'deposit' => $quote->total_deposit,
            'total_amount' => $quote->payable_amount,
            'total_credit_amount' => $quote->total_credit_amount,
            'apr' => $quote->apr,
            'interest_rate' => $quote->interest_rate,
            'number_of_months' => $quote->term,
            'cash_price' => $quote->cash_price,
            'dealer_deposit' => $quote->dealer_deposit,
            'customer_deposit' => $quote->customer_deposit,
            'payable_amount' => $quote->payable_amount,
            'option_to_purchase_fee' => $quote->option_to_purchase_fee,
            'documentation_fee' => $quote->documentation_fee,
            'excess_mileage_charge' => $quote->excess_mileage_charge,
            'terms_and_conditions' => $quote->terms_and_conditions,
            'data' => $quote->data,
        ]));

        // Reset payment type since finance selection is lost
        $deal->update(['payment_type' => null]);
        $this->updateDealTotals($deal);
    }

    private function updateDealTotals(Deal $deal): void
    {
        $deal->load('partExchange');

        $subtotal = $deal->vehicle->price + $deal->addOns()->sum('price');
        $part_ex_contribution = $deal->partExchange->sum('retail_price')
            - $deal->partExchange->sum('outstanding_finance')
            - $deal->partExchange->sum('cashback_amount');
        // Total cannot be negative - if part exchange exceeds subtotal, total is 0
        $total_amount = max(0, $subtotal - $part_ex_contribution);
        $deposit_amount = max(0, $part_ex_contribution + ($deal->customer_deposit ?? 0));
        $payable_amount = match ($deal->payment_type) {
            DealPaymentType::ENQUIRY => 0,
            DealPaymentType::DEPOSIT => $deposit_amount,
            DealPaymentType::FULL_PAYMENT => $total_amount,
            default => $deal->vehicle->getReservationAmount(),
        };
        $deal->update([
            'part_ex_contribution' => $part_ex_contribution,
            'total_amount' => $total_amount,
            'deposit_amount' => $deposit_amount,
            'payable_amount' => $payable_amount,
        ]);
    }

    private function isAddOnApplicableToVehicle(AddOn $addOn, Vehicle $vehicle): bool
    {
        if ($addOn->conditions->isEmpty()) {
            return true;
        }

        $conditions_matching_vehicle = $addOn->conditions
            ->filter(fn(AddOnCondition $condition) => $this->conditionMatchesVehicle($condition, $vehicle));

        return $addOn->conditions->count() === $conditions_matching_vehicle->count();
    }

    private function conditionMatchesVehicle(AddOnCondition $condition, Vehicle $vehicle): bool
    {
        return match ($condition->condition) {
            '=' => $vehicle->getAttribute($condition->field) == $condition->value,
            '!=' => $vehicle->getAttribute($condition->field) != $condition->value,
            default => false,
        };
    }
}
