<?php

namespace Tenant;

use App\Facades\Feature;
use App\Repositories\DealBuilderRepository;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Mtc\MercuryDataModels\DealBuilder\AddOn;
use Mtc\MercuryDataModels\DealBuilder\Deal;
use Mtc\MercuryDataModels\DealBuilder\DealPartExchange;
use Mtc\MercuryDataModels\DealBuilder\DealPaymentType;
use Mtc\MercuryDataModels\DealBuilder\Status;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleValuation;
use Tests\TenantTestCase;

class DealBuilderRepositoryTest extends TenantTestCase
{
    private DealBuilderRepository $repository;

    protected function setUp(): void
    {
        parent::setUp();
        Feature::shouldReceive('isEnabled')
            ->zeroOrMoreTimes()
            ->andReturn(true);
        $this->repository = new DealBuilderRepository();
    }

    public function testGetOrCreateCreatesNewDeal(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 25000, 'is_published' => true]);

        $deal = $this->repository->getOrCreate(null, $vehicle->slug);

        $this->assertInstanceOf(Deal::class, $deal);
        $this->assertEquals($vehicle->id, $deal->vehicle_id);
        $this->assertEquals(0, $deal->status_id);
        $this->assertEquals(25000, $deal->total_amount);
        $this->assertEquals($deal->id, Session::get(DealBuilderRepository::DEAL_SESSION_KEY));
    }

    public function testGetOrCreateReturnsExistingDealForSameVehicle(): void
    {
        $vehicle = Vehicle::factory()->create(['is_published' => true]);
        $existingDeal = Deal::factory()->create(['vehicle_id' => $vehicle->id]);

        $deal = $this->repository->getOrCreate($existingDeal->id, $vehicle->slug);

        $this->assertEquals($existingDeal->id, $deal->id);
    }

    public function testGetOrCreateThrowsForDifferentVehicleWithoutForce(): void
    {
        $vehicle1 = Vehicle::factory()->create(['is_published' => true]);
        $vehicle2 = Vehicle::factory()->create(['is_published' => true]);
        $existingDeal = Deal::factory()->create(['vehicle_id' => $vehicle1->id]);

        $this->expectException(ValidationException::class);

        $this->repository->getOrCreate($existingDeal->id, $vehicle2->slug, false);
    }

    public function testGetOrCreateAllowsDifferentVehicleWithForce(): void
    {
        $vehicle1 = Vehicle::factory()->create(['is_published' => true]);
        $vehicle2 = Vehicle::factory()->create(['is_published' => true]);
        $existingDeal = Deal::factory()->create(['vehicle_id' => $vehicle1->id]);

        $deal = $this->repository->getOrCreate($existingDeal->id, $vehicle2->slug, true);

        $this->assertNotEquals($existingDeal->id, $deal->id);
        $this->assertEquals($vehicle2->id, $deal->vehicle_id);
    }

    public function testGetOrCreateClearsSessionForSubmittedDeal(): void
    {
        $vehicle = Vehicle::factory()->create(['is_published' => true]);
        $submittedDeal = Deal::factory()->submitted()->create(['vehicle_id' => $vehicle->id]);

        Session::put(DealBuilderRepository::DEAL_SESSION_KEY, $submittedDeal->id);

        $deal = $this->repository->getOrCreate($submittedDeal->id, $vehicle->slug);

        $this->assertNotEquals($submittedDeal->id, $deal->id);
    }

    public function testAddAddonUpdatesDealTotals(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 20000]);
        $deal = Deal::factory()->create([
            'vehicle_id' => $vehicle->id,
            'total_amount' => 20000,
        ]);
        $addOn = AddOn::factory()->create(['price' => 500]);

        $this->repository->addAddOn($deal, $addOn->id, 'total');

        $deal->refresh();
        $this->assertEquals(20500, $deal->total_amount);
        $this->assertDatabaseHas('deal_add_ons', [
            'deal_id' => $deal->id,
            'add_on_id' => $addOn->id,
            'price' => 500,
        ]);
    }

    public function testRemoveAddonUpdatesDealTotals(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 20000]);
        $deal = Deal::factory()->create([
            'vehicle_id' => $vehicle->id,
            'total_amount' => 20500,
        ]);
        $addOn = AddOn::factory()->create(['price' => 500]);
        $deal->addOns()->create([
            'add_on_id' => $addOn->id,
            'price' => 500,
            'payment_term' => 'total',
        ]);

        $this->repository->removeAddOn($deal, $addOn->id);

        $deal->refresh();
        $this->assertEquals(20000, $deal->total_amount);
        $this->assertDatabaseMissing('deal_add_ons', [
            'deal_id' => $deal->id,
            'add_on_id' => $addOn->id,
        ]);
    }

    public function testAddPartExchangeLimitsWhenMultipleDisabled(): void
    {
        $vehicle = Vehicle::factory()->create(['is_published' => true]);
        $deal = Deal::factory()->create(['vehicle_id' => $vehicle->id]);
        DealPartExchange::factory()->create(['deal_id' => $deal->id]);
        $valuation = VehicleValuation::factory()->create();

        $this->expectException(ValidationException::class);

        $this->repository->addPartExchange($deal, [
            'valuation_id' => $valuation->id,
            'registration' => 'AB12 CDE',
            'mileage' => 50000,
            'outstanding_finance' => 0,
            'cashback_amount' => 0,
            'email' => 'test@example.com',
        ]);
    }

    public function testPartExchangeReducesTotalAmount(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 25000, 'is_published' => true]);
        $deal = Deal::factory()->create([
            'vehicle_id' => $vehicle->id,
            'total_amount' => 25000,
        ]);
        DealPartExchange::factory()->create([
            'deal_id' => $deal->id,
            'retail_price' => 10000,
            'outstanding_finance' => 2000,
            'cashback_amount' => 1000,
        ]);

        // Manually trigger update since we created the part exchange directly
        $this->invokeUpdateDealTotals($deal);

        $deal->refresh();
        // Part ex contribution: 10000 - 2000 - 1000 = 7000
        // Total: 25000 - 7000 = 18000
        $this->assertEquals(18000, $deal->total_amount);
        $this->assertEquals(7000, $deal->part_ex_contribution);
    }

    public function testTotalAmountCannotGoNegative(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 15000]);
        $deal = Deal::factory()->create([
            'vehicle_id' => $vehicle->id,
            'total_amount' => 15000,
        ]);
        DealPartExchange::factory()->create([
            'deal_id' => $deal->id,
            'retail_price' => 20000,
            'outstanding_finance' => 0,
            'cashback_amount' => 0,
        ]);

        $this->invokeUpdateDealTotals($deal);

        $deal->refresh();
        // Part exchange (20000) exceeds vehicle price (15000)
        // Total should be 0, not -5000
        $this->assertEquals(0, $deal->total_amount);
    }

    public function testSetPaymentTypeCalculatesPayableAmount(): void
    {
        $vehicle = Vehicle::factory()->create(['price' => 25000, 'is_published' => true]);
        $deal = Deal::factory()->create([
            'vehicle_id' => $vehicle->id,
            'total_amount' => 25000,
            'deposit_amount' => 5000,
        ]);

        $this->repository->setPaymentType($deal, 'deposit');

        $deal->refresh();
        $this->assertEquals(DealPaymentType::DEPOSIT, $deal->payment_type);
        $this->assertEquals(5000, $deal->payable_amount);

        $this->repository->setPaymentType($deal, 'full_payment');

        $deal->refresh();
        $this->assertEquals(DealPaymentType::FULL_PAYMENT, $deal->payment_type);
        $this->assertEquals(25000, $deal->payable_amount);

        $this->repository->setPaymentType($deal, 'enquiry');

        $deal->refresh();
        $this->assertEquals(DealPaymentType::ENQUIRY, $deal->payment_type);
        $this->assertEquals(0, $deal->payable_amount);
    }

    public function testSubmitDealSetsInitialStatus(): void
    {
        $vehicle = Vehicle::factory()->create(['is_published' => true]);
        $deal = Deal::factory()->create(['vehicle_id' => $vehicle->id]);
        $initialStatus = Status::factory()->initial()->create();

        Session::put(DealBuilderRepository::DEAL_SESSION_KEY, $deal->id);

        $this->repository->submitDeal($deal, [
            'first_name' => 'John',
            'last_name' => 'Doe',
            'email' => 'john@example.com',
            'contact_number' => '07123456789',
        ]);

        $deal->refresh();
        $this->assertEquals('John', $deal->first_name);
        $this->assertEquals('Doe', $deal->last_name);
        $this->assertEquals('john@example.com', $deal->email);
        $this->assertEquals($initialStatus->id, $deal->status_id);
        $this->assertNull(Session::get(DealBuilderRepository::DEAL_SESSION_KEY));
    }

    public function testSubmitDealCreatesHistoryEntry(): void
    {
        $vehicle = Vehicle::factory()->create(['is_published' => true]);
        $deal = Deal::factory()->create(['vehicle_id' => $vehicle->id]);
        $initialStatus = Status::factory()->initial()->create();

        $this->repository->submitDeal($deal, [
            'first_name' => 'John',
            'last_name' => 'Doe',
            'email' => 'john@example.com',
            'contact_number' => '07123456789',
        ]);

        $this->assertDatabaseHas('deal_history', [
            'deal_id' => $deal->id,
            'status_id' => $initialStatus->id,
        ]);
    }

    /**
     * Helper method to invoke the private updateDealTotals method
     */
    private function invokeUpdateDealTotals(Deal $deal): void
    {
        $reflection = new \ReflectionClass($this->repository);
        $method = $reflection->getMethod('updateDealTotals');
        $method->setAccessible(true);
        $method->invoke($this->repository, $deal);
    }
}
