<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\Jobs\DetachAutoTraderReferences;
use App\Jobs\RemoveAutoTraderReferences;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Http;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\SalesChannelHistory;
use Mtc\MercuryDataModels\Vehicle;
use Tests\TenantTestCase;
use Tests\UserForTenant;

class DetachAutoTraderReferencesTest extends TenantTestCase
{
    use UserForTenant;

    private const LOCATION_ID = 'AT-LOC-1';

    // --- Endpoint ------------------------------------------------------------------

    public function testEndpointDispatchesDetachJobAndLogsHistory(): void
    {
        Bus::fake([DetachAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        Vehicle::factory()->create(['auto_trader_id' => 'STK1', 'dealership_id' => $dealership->id]);
        Vehicle::factory()->create(['auto_trader_id' => 'STK2', 'dealership_id' => $dealership->id]);
        Vehicle::factory()->create(['auto_trader_id' => null, 'dealership_id' => $dealership->id]);
        // Linked vehicle at another location is not counted.
        Vehicle::factory()->create([
            'auto_trader_id' => 'STK3',
            'dealership_id' => $this->makeDealership('AT-LOC-2')->id,
        ]);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('tenant.sales-channels.detach-references', [
                'channel' => 'auto-trader-sales',
                'locationId' => self::LOCATION_ID,
            ]));

        $response->assertOk()
            ->assertJson([
                'success' => true,
                'vehicle_count' => 2,
            ]);

        Bus::assertDispatched(DetachAutoTraderReferences::class);

        $this->assertDatabaseHas((new SalesChannelHistory())->getTable(), [
            'channel' => 'auto-trader-sales',
            'was_successful' => true,
        ]);
    }

    public function testEndpointRejectsOtherChannels(): void
    {
        Bus::fake([DetachAutoTraderReferences::class]);
        $this->makeDealership(self::LOCATION_ID);

        $this->actingAs($this->getUser())
            ->postJson(route('tenant.sales-channels.detach-references', [
                'channel' => 'car-gurus',
                'locationId' => self::LOCATION_ID,
            ]))
            ->assertNotFound();

        Bus::assertNotDispatched(DetachAutoTraderReferences::class);
    }

    public function testEndpointRejectsUnknownLocation(): void
    {
        Bus::fake([DetachAutoTraderReferences::class]);
        $this->makeDealership(self::LOCATION_ID);

        $this->actingAs($this->getUser())
            ->postJson(route('tenant.sales-channels.detach-references', [
                'channel' => 'auto-trader-sales',
                'locationId' => 'AT-LOC-MISSING',
            ]))
            ->assertNotFound();

        Bus::assertNotDispatched(DetachAutoTraderReferences::class);
    }

    // --- Job -----------------------------------------------------------------------

    public function testJobNotifiesHubAndDispatchesReferenceRemovalPerVehicle(): void
    {
        $this->makeSetting('auto-trader-hub-token', 'test-token', 'string');
        Http::fake(['*clear-references' => Http::response(['status' => true], 200)]);
        Bus::fake([RemoveAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        $linked = Vehicle::factory()->create(['auto_trader_id' => 'STK1', 'dealership_id' => $dealership->id]);
        $trashed = Vehicle::factory()->create(['auto_trader_id' => 'STK2', 'dealership_id' => $dealership->id]);
        $trashed->delete();
        $unlinked = Vehicle::factory()->create(['auto_trader_id' => null, 'dealership_id' => $dealership->id]);
        $otherLocation = Vehicle::factory()->create([
            'auto_trader_id' => 'STK3',
            'dealership_id' => $this->makeDealership('AT-LOC-2')->id,
        ]);

        (new DetachAutoTraderReferences(self::LOCATION_ID))->handle();

        Http::assertSent(function ($request) use ($linked, $trashed) {
            return str_contains($request->url(), 'clear-references')
                && collect($request['vehicle_ids'])->sort()->values()->all()
                    === collect([$linked->id, $trashed->id])->sort()->values()->all();
        });

        Bus::assertDispatchedTimes(RemoveAutoTraderReferences::class, 2);
        collect([$unlinked->id, $otherLocation->id])->each(
            fn (int $excludedId) => Bus::assertNotDispatched(
                RemoveAutoTraderReferences::class,
                fn (RemoveAutoTraderReferences $job) => $this->jobVehicleId($job) === $excludedId,
            ),
        );
    }

    public function testJobStillClearsLocallyWithoutHubToken(): void
    {
        Http::fake();
        Bus::fake([RemoveAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        Vehicle::factory()->create(['auto_trader_id' => 'STK1', 'dealership_id' => $dealership->id]);

        (new DetachAutoTraderReferences(self::LOCATION_ID))->handle();

        Http::assertNothingSent();
        Bus::assertDispatchedTimes(RemoveAutoTraderReferences::class, 1);
    }

    public function testJobSkipsHubNotifyForFtpMethod(): void
    {
        $this->makeSetting('auto-trader-hub-token', 'test-token', 'string');
        $this->makeSetting('sales-channels-auto-trader-method', 'ftp', 'string');
        Http::fake();
        Bus::fake([RemoveAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        Vehicle::factory()->create(['auto_trader_id' => 'STK1', 'dealership_id' => $dealership->id]);

        (new DetachAutoTraderReferences(self::LOCATION_ID))->handle();

        Http::assertNothingSent();
        Bus::assertDispatchedTimes(RemoveAutoTraderReferences::class, 1);
    }

    public function testJobFailsBeforeClearingWhenHubRejectsNotification(): void
    {
        $this->makeSetting('auto-trader-hub-token', 'test-token', 'string');
        Http::fake(['*clear-references' => Http::response(['status' => false], 500)]);
        Bus::fake([RemoveAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        $vehicle = Vehicle::factory()->create(['auto_trader_id' => 'STK1', 'dealership_id' => $dealership->id]);

        $this->expectException(\RuntimeException::class);

        try {
            (new DetachAutoTraderReferences(self::LOCATION_ID))->handle();
        } finally {
            Bus::assertNotDispatched(RemoveAutoTraderReferences::class);
            $this->assertEquals('STK1', $vehicle->fresh()->auto_trader_id);
        }
    }

    public function testJobDoesNothingWithoutLinkedVehiclesAtLocation(): void
    {
        $this->makeSetting('auto-trader-hub-token', 'test-token', 'string');
        Http::fake();
        Bus::fake([RemoveAutoTraderReferences::class]);

        $dealership = $this->makeDealership(self::LOCATION_ID);
        Vehicle::factory()->create(['auto_trader_id' => null, 'dealership_id' => $dealership->id]);
        Vehicle::factory()->create([
            'auto_trader_id' => 'STK1',
            'dealership_id' => $this->makeDealership('AT-LOC-2')->id,
        ]);

        (new DetachAutoTraderReferences(self::LOCATION_ID))->handle();

        Http::assertNothingSent();
        Bus::assertNotDispatched(RemoveAutoTraderReferences::class);
    }

    // --- Helpers ---------------------------------------------------------------------

    private function makeDealership(string $locationId): Dealership
    {
        return Dealership::factory()->create(['data' => ['auto-trader-location-id' => $locationId]]);
    }

    private function makeSetting(string $key, mixed $value, string $type = 'boolean'): void
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'foo',
            'group' => 'foo',
            'name' => 'foo',
            'config_key' => $key,
            'value' => $value,
            'type' => $type,
        ]);
    }

    private function jobVehicleId(RemoveAutoTraderReferences $job): int
    {
        $property = new \ReflectionProperty($job, 'vehicle');
        return $property->getValue($job)->id;
    }
}
