<?php

namespace Tests\Tenant;

use App\Facades\Feature;
use Illuminate\Support\Facades\Auth;
use Mockery;
use Mockery\MockInterface;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\User;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;
use Tests\TenantTestCase;

class DealershipAccessMiddlewareRouteTest extends TenantTestCase
{
    private MockInterface $adminUser;
    private MockInterface $restrictedUser;
    private MockInterface $noAccessUser;
    private Dealership $dealership1;
    private Dealership $dealership2;
    private Vehicle $vehicleAtDealership1;
    private Vehicle $vehicleAtDealership2;
    private VehicleOffer $offerAtDealership1;
    private VehicleOffer $offerAtDealership2;
    private NewCar $newCarAtDealership1;
    private NewCar $newCarAtDealership2;

    protected function setUp(): void
    {
        parent::setUp();

        Feature::setEnabled(tenant(), ['dealership-access']);

        $this->createTestData();
    }

    private function createTestData(): void
    {
        // Create dealerships
        $this->dealership1 = Dealership::factory()->create(['name' => 'Dealership 1']);
        $this->dealership2 = Dealership::factory()->create(['name' => 'Dealership 2']);

        // Create vehicles at each dealership
        $this->vehicleAtDealership1 = Vehicle::factory()->create([
            'dealership_id' => $this->dealership1->id,
        ]);
        $this->vehicleAtDealership2 = Vehicle::factory()->create([
            'dealership_id' => $this->dealership2->id,
        ]);

        // Create offers at each dealership
        $this->offerAtDealership1 = VehicleOffer::factory()->create([
            'dealership_id' => $this->dealership1->id,
        ]);
        $this->offerAtDealership2 = VehicleOffer::factory()->create([
            'dealership_id' => $this->dealership2->id,
        ]);

        // Create new cars at each dealership
        $this->newCarAtDealership1 = NewCar::factory()->create([
            'dealership_id' => $this->dealership1->id,
        ]);
        $this->newCarAtDealership2 = NewCar::factory()->create([
            'dealership_id' => $this->dealership2->id,
        ]);

        // Create mock users
        $this->adminUser = $this->createMockUser(1, true, [$this->dealership1->id, $this->dealership2->id]);
        $this->restrictedUser = $this->createMockUser(2, false, [$this->dealership1->id]);
        $this->noAccessUser = $this->createMockUser(3, false, []);
    }

    private function createMockUser(int $id, bool $isAdmin, array $accessibleDealershipIds): MockInterface
    {
        $mock = Mockery::mock(User::class)->makePartial();
        $mock->shouldAllowMockingProtectedMethods();
        $mock->setAttribute('id', $id);

        $mock->shouldReceive('hasRole')
            ->with(['mtc', 'Administrator'])
            ->andReturn($isAdmin);

        $mock->shouldReceive('hasRole')
            ->andReturn(true);

        $mock->shouldReceive('hasPermissionTo')
            ->andReturn(true);

        $mock->shouldReceive('getAccessibleDealershipIds')
            ->andReturn($accessibleDealershipIds);

        $mock->shouldReceive('hasAccessToDealership')
            ->andReturnUsing(function (int $dealershipId) use ($isAdmin, $accessibleDealershipIds) {
                if ($isAdmin) {
                    return true;
                }
                return in_array($dealershipId, $accessibleDealershipIds);
            });

        $mock->shouldReceive('latestUnreadReleaseNote')
            ->andReturn(null);

        return $mock;
    }

    // ==================== Vehicle Show Route Tests ====================

    public function testAdminUserCanViewVehicleAtAnyDealership(): void
    {
        Auth::setUser($this->adminUser);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership1->id));

        $response->assertStatus(200);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership2->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCanViewVehicleAtAssignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership1->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCannotViewVehicleAtUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership2->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    public function testUserWithNoAccessCannotViewAnyVehicle(): void
    {
        Auth::setUser($this->noAccessUser);

        $response = $this->actingAs($this->noAccessUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership1->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Vehicle Update Route Tests ====================

    public function testRestrictedUserCannotUpdateVehicleAtUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicles.update', $this->vehicleAtDealership2->id), [
                'colour' => 'SHOULD_NOT_UPDATE',
            ]);

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Vehicle Delete Route Tests ====================

    public function testRestrictedUserCanDeleteVehicleAtAssignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $vehicle = Vehicle::factory()->create([
            'dealership_id' => $this->dealership1->id,
        ]);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->deleteJson(route('tenant.vehicles.destroy', $vehicle->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCannotDeleteVehicleAtUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->deleteJson(route('tenant.vehicles.destroy', $this->vehicleAtDealership2->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Vehicle Index (List) Route Tests ====================

    public function testAdminUserVehicleIndexReturnsAllVehicles(): void
    {
        Auth::setUser($this->adminUser);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $this->assertCount(2, $response->json('data'));
    }

    public function testRestrictedUserVehicleIndexReturnsOnlyAccessibleVehicles(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);

        // Should only see vehicles from dealership1
        $vehicleIds = collect($response->json('data'))->pluck('id')->toArray();
        $this->assertContains($this->vehicleAtDealership1->id, $vehicleIds);
        $this->assertNotContains($this->vehicleAtDealership2->id, $vehicleIds);
    }

    public function testUserWithNoAccessVehicleIndexReturnsNoVehicles(): void
    {
        Auth::setUser($this->noAccessUser);

        $response = $this->actingAs($this->noAccessUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $this->assertCount(0, $response->json('data'));
    }

    // ==================== Vehicle Offer Show Route Tests ====================

    public function testAdminUserCanViewOfferAtAnyDealership(): void
    {
        Auth::setUser($this->adminUser);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $this->offerAtDealership1->id));

        $response->assertStatus(200);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $this->offerAtDealership2->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCanViewOfferAtAssignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $this->offerAtDealership1->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCannotViewOfferAtUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $this->offerAtDealership2->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Vehicle Offer Index (List) Route Tests ====================

    public function testRestrictedUserOfferIndexReturnsOnlyAccessibleOffers(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.index'));

        $response->assertStatus(200);

        // Should only see offers from dealership1
        $offerIds = collect($response->json('data'))->pluck('id')->toArray();
        $this->assertContains($this->offerAtDealership1->id, $offerIds);
        $this->assertNotContains($this->offerAtDealership2->id, $offerIds);
    }

    // ==================== New Car Show Route Tests ====================

    public function testAdminUserCanViewNewCarAtAnyDealership(): void
    {
        Auth::setUser($this->adminUser);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.new-cars.show', $this->newCarAtDealership1->id));

        $response->assertStatus(200);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.new-cars.show', $this->newCarAtDealership2->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCanViewNewCarAtAssignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.new-cars.show', $this->newCarAtDealership1->id));

        $response->assertStatus(200);
    }

    public function testRestrictedUserCannotViewNewCarAtUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.new-cars.show', $this->newCarAtDealership2->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Dealership Show Route Tests ====================

    public function testRestrictedUserCannotViewUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.dealerships.show', $this->dealership2->id));

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Dealership Update Route Tests ====================

    public function testRestrictedUserCannotUpdateUnassignedDealership(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $this->dealership2->id), [
                'name' => 'Should Not Update',
            ]);

        $response->assertStatus(403);
        $response->assertJson(['message' => 'You do not have access to this dealership']);
    }

    // ==================== Dealership Index (List) Route Tests ====================

    public function testAdminUserDealershipIndexReturnsAllDealerships(): void
    {
        Auth::setUser($this->adminUser);

        $response = $this->actingAs($this->adminUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.dealerships.index'));

        $response->assertStatus(200);
        $this->assertCount(2, $response->json('data'));
    }

    public function testRestrictedUserDealershipIndexReturnsOnlyAccessibleDealerships(): void
    {
        Auth::setUser($this->restrictedUser);

        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.dealerships.index'));

        $response->assertStatus(200);

        // Should only see dealership1
        $dealershipIds = collect($response->json('data'))->pluck('id')->toArray();
        $this->assertContains($this->dealership1->id, $dealershipIds);
        $this->assertNotContains($this->dealership2->id, $dealershipIds);
    }

    public function testUserWithNoAccessDealershipIndexReturnsNoDealerships(): void
    {
        Auth::setUser($this->noAccessUser);

        $response = $this->actingAs($this->noAccessUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.dealerships.index'));

        $response->assertStatus(200);
        $this->assertCount(0, $response->json('data'));
    }

    // ==================== Feature Flag Disabled Tests ====================

    public function testMiddlewareHasNoEffectWhenFeatureIsDisabled(): void
    {
        Feature::swap(new \Tests\TestFeatureRepository());

        Auth::setUser($this->restrictedUser);

        // Restricted user can view a vehicle at an unassigned dealership when the feature is off
        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.show', $this->vehicleAtDealership2->id));

        $response->assertStatus(200);

        // Index returns all vehicles when the feature is off
        $response = $this->actingAs($this->restrictedUser)
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicles.index'));

        $response->assertStatus(200);
        $this->assertCount(2, $response->json('data'));
    }
}
