<?php

namespace Tests\Tenant;

use Mockery;
use Mockery\MockInterface;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\User;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;
use Tests\TenantTestCase;

class DealershipAccessScopeTest extends TenantTestCase
{
    private MockInterface $adminUser;
    private MockInterface $restrictedUser;
    private MockInterface $noAccessUser;
    private Dealership $dealership1;
    private Dealership $dealership2;
    private Dealership $dealership3;
    private Franchise $franchise1;
    private Franchise $franchise2;

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

        $this->createTestData();
    }

    private function createTestData(): void
    {
        // Create franchises
        $this->franchise1 = Franchise::factory()->create(['name' => 'Franchise 1']);
        $this->franchise2 = Franchise::factory()->create(['name' => 'Franchise 2']);

        // Create dealerships with franchise assignments
        // Dealership 1 & 2 belong to franchise1, dealership3 belongs to franchise2
        $this->dealership1 = Dealership::factory()->create([
            'name' => 'Dealership 1',
            'franchise_id' => $this->franchise1->id,
        ]);
        $this->dealership2 = Dealership::factory()->create([
            'name' => 'Dealership 2',
            'franchise_id' => $this->franchise1->id,
        ]);
        $this->dealership3 = Dealership::factory()->create([
            'name' => 'Dealership 3',
            'franchise_id' => $this->franchise2->id,
        ]);

        // Create mock users (User model uses CentralConnection so can't be created in tenant SQLite)
        $allDealershipIds = [$this->dealership1->id, $this->dealership2->id, $this->dealership3->id];
        $allFranchiseIds = [$this->franchise1->id, $this->franchise2->id];

        $this->adminUser = $this->createMockUser(1, true, $allDealershipIds, $allFranchiseIds);
        $this->restrictedUser = $this->createMockUser(
            2,
            false,
            [$this->dealership1->id, $this->dealership2->id],
            [$this->franchise1->id]
        );
        $this->noAccessUser = $this->createMockUser(3, false, [], []);

        // Create vehicles at each dealership
        Vehicle::factory()->count(2)->create(['dealership_id' => $this->dealership1->id]);
        Vehicle::factory()->count(3)->create(['dealership_id' => $this->dealership2->id]);
        Vehicle::factory()->count(4)->create(['dealership_id' => $this->dealership3->id]);

        // Create offers at each dealership
        VehicleOffer::factory()->count(2)->create(['dealership_id' => $this->dealership1->id]);
        VehicleOffer::factory()->count(3)->create(['dealership_id' => $this->dealership2->id]);
        VehicleOffer::factory()->count(4)->create(['dealership_id' => $this->dealership3->id]);

        // Create new cars at each dealership
        NewCar::factory()->count(2)->create(['dealership_id' => $this->dealership1->id]);
        NewCar::factory()->count(3)->create(['dealership_id' => $this->dealership2->id]);
        NewCar::factory()->count(4)->create(['dealership_id' => $this->dealership3->id]);
    }

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

        // Set id using setAttribute to work with Eloquent
        $mock->setAttribute('id', $id);

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

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

        $mock->shouldReceive('getAccessibleFranchiseIds')
            ->andReturn($accessibleFranchiseIds);

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

        $mock->shouldReceive('hasAccessToFranchise')
            ->andReturnUsing(function (int $franchiseId) use ($isAdmin, $accessibleFranchiseIds) {
                if ($isAdmin) {
                    return true;
                }
                return in_array($franchiseId, $accessibleFranchiseIds);
            });

        return $mock;
    }

    // ==================== Vehicle Tests ====================

    public function testAdminUserCanSeeAllVehicles(): void
    {
        $vehicles = Vehicle::query()->forUser($this->adminUser)->get();

        $this->assertCount(9, $vehicles);
    }

    public function testRestrictedUserCanOnlySeeVehiclesAtAssignedDealerships(): void
    {
        $vehicles = Vehicle::query()->forUser($this->restrictedUser)->get();

        $this->assertCount(5, $vehicles); // 2 from dealership1 + 3 from dealership2

        $dealershipIds = $vehicles->pluck('dealership_id')->unique()->toArray();
        $this->assertContains($this->dealership1->id, $dealershipIds);
        $this->assertContains($this->dealership2->id, $dealershipIds);
        $this->assertNotContains($this->dealership3->id, $dealershipIds);
    }

    public function testUserWithNoDealershipAccessSeesNoVehicles(): void
    {
        $vehicles = Vehicle::query()->forUser($this->noAccessUser)->get();

        $this->assertCount(0, $vehicles);
    }

    // ==================== VehicleOffer Tests ====================

    public function testAdminUserCanSeeAllOffers(): void
    {
        $offers = VehicleOffer::query()->forUser($this->adminUser)->get();

        $this->assertCount(9, $offers);
    }

    public function testRestrictedUserCanOnlySeeOffersAtAssignedDealerships(): void
    {
        $offers = VehicleOffer::query()->forUser($this->restrictedUser)->get();

        $this->assertCount(5, $offers); // 2 from dealership1 + 3 from dealership2

        $dealershipIds = $offers->pluck('dealership_id')->unique()->toArray();
        $this->assertContains($this->dealership1->id, $dealershipIds);
        $this->assertContains($this->dealership2->id, $dealershipIds);
        $this->assertNotContains($this->dealership3->id, $dealershipIds);
    }

    public function testUserWithNoDealershipAccessSeesNoOffers(): void
    {
        $offers = VehicleOffer::query()->forUser($this->noAccessUser)->get();

        $this->assertCount(0, $offers);
    }

    // ==================== NewCar Tests ====================

    public function testAdminUserCanSeeAllNewCars(): void
    {
        $newCars = NewCar::query()->forUser($this->adminUser)->get();

        $this->assertCount(9, $newCars);
    }

    public function testRestrictedUserCanOnlySeeNewCarsAtAssignedDealerships(): void
    {
        $newCars = NewCar::query()->forUser($this->restrictedUser)->get();

        $this->assertCount(5, $newCars); // 2 from dealership1 + 3 from dealership2

        $dealershipIds = $newCars->pluck('dealership_id')->unique()->toArray();
        $this->assertContains($this->dealership1->id, $dealershipIds);
        $this->assertContains($this->dealership2->id, $dealershipIds);
        $this->assertNotContains($this->dealership3->id, $dealershipIds);
    }

    public function testUserWithNoDealershipAccessSeesNoNewCars(): void
    {
        $newCars = NewCar::query()->forUser($this->noAccessUser)->get();

        $this->assertCount(0, $newCars);
    }

    // ==================== Dealership Tests ====================

    public function testAdminUserCanSeeAllDealerships(): void
    {
        $dealerships = Dealership::query()->forUser($this->adminUser)->get();

        $this->assertCount(3, $dealerships);
    }

    public function testRestrictedUserCanOnlySeeAssignedDealerships(): void
    {
        $dealerships = Dealership::query()->forUser($this->restrictedUser)->get();

        $this->assertCount(2, $dealerships);

        $dealershipIds = $dealerships->pluck('id')->toArray();
        $this->assertContains($this->dealership1->id, $dealershipIds);
        $this->assertContains($this->dealership2->id, $dealershipIds);
        $this->assertNotContains($this->dealership3->id, $dealershipIds);
    }

    public function testUserWithNoDealershipAccessSeesNoDealerships(): void
    {
        $dealerships = Dealership::query()->forUser($this->noAccessUser)->get();

        $this->assertCount(0, $dealerships);
    }

    // ==================== Helper Method Tests ====================

    public function testUserHasAccessToDealershipReturnsTrueForAssignedDealership(): void
    {
        $this->assertTrue($this->restrictedUser->hasAccessToDealership($this->dealership1->id));
        $this->assertTrue($this->restrictedUser->hasAccessToDealership($this->dealership2->id));
    }

    public function testUserHasAccessToDealershipReturnsFalseForUnassignedDealership(): void
    {
        $this->assertFalse($this->restrictedUser->hasAccessToDealership($this->dealership3->id));
    }

    public function testAdminUserHasAccessToAllDealerships(): void
    {
        $this->assertTrue($this->adminUser->hasAccessToDealership($this->dealership1->id));
        $this->assertTrue($this->adminUser->hasAccessToDealership($this->dealership2->id));
        $this->assertTrue($this->adminUser->hasAccessToDealership($this->dealership3->id));
    }

    public function testGetAccessibleDealershipIdsReturnsCorrectIds(): void
    {
        $ids = $this->restrictedUser->getAccessibleDealershipIds();

        $this->assertCount(2, $ids);
        $this->assertContains($this->dealership1->id, $ids);
        $this->assertContains($this->dealership2->id, $ids);
        $this->assertNotContains($this->dealership3->id, $ids);
    }

    // ==================== Scope Chaining Tests ====================

    public function testForUserScopeCanBeChainedWithOtherScopes(): void
    {
        // Create a published vehicle at dealership1
        Vehicle::factory()->create([
            'dealership_id' => $this->dealership1->id,
            'is_published' => true,
        ]);

        // Create an unpublished vehicle at dealership1
        Vehicle::factory()->create([
            'dealership_id' => $this->dealership1->id,
            'is_published' => false,
        ]);

        $publishedVehicles = Vehicle::query()
            ->forUser($this->restrictedUser)
            ->active()
            ->get();

        // Should only include published vehicles from dealership1 and dealership2
        foreach ($publishedVehicles as $vehicle) {
            $this->assertTrue($vehicle->is_published);
            $this->assertContains($vehicle->dealership_id, [$this->dealership1->id, $this->dealership2->id]);
        }
    }

    // ==================== Franchise Access Tests ====================

    public function testRestrictedUserCanSeeOffersWithNullDealershipButAccessibleFranchise(): void
    {
        // Create offer with null dealership but franchise1 (which user has access to via dealerships)
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise1->id,
        ]);

        // Create offer with null dealership but franchise2 (which user does NOT have access to)
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise2->id,
        ]);

        $offers = VehicleOffer::query()->forUser($this->restrictedUser)->get();

        // Should see: 2 from d1 + 3 from d2 + 1 from franchise1 with null dealership = 6
        $this->assertCount(6, $offers);

        // Verify the franchise-only offer is included
        $franchiseOnlyOffers = $offers->whereNull('dealership_id');
        $this->assertCount(1, $franchiseOnlyOffers);
        $this->assertEquals($this->franchise1->id, $franchiseOnlyOffers->first()->franchise_id);
    }

    public function testRestrictedUserCannotSeeOffersWithNullDealershipAndInaccessibleFranchise(): void
    {
        // Create offer with null dealership but franchise2 (which user does NOT have access to)
        $inaccessibleOffer = VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise2->id,
        ]);

        $offers = VehicleOffer::query()->forUser($this->restrictedUser)->get();

        $this->assertNotContains($inaccessibleOffer->id, $offers->pluck('id')->toArray());
    }

    public function testAdminUserCanSeeAllOffersIncludingFranchiseOnly(): void
    {
        // Create offers with null dealership for both franchises
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise1->id,
        ]);
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise2->id,
        ]);

        $offers = VehicleOffer::query()->forUser($this->adminUser)->get();

        // Should see all: 2+3+4 from dealerships + 2 franchise-only = 11
        $this->assertCount(11, $offers);
    }

    public function testRestrictedUserCanSeeNewCarsWithNullDealershipButAccessibleFranchise(): void
    {
        // Create new car with null dealership but franchise1 (accessible)
        NewCar::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise1->id,
        ]);

        // Create new car with null dealership but franchise2 (not accessible)
        NewCar::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise2->id,
        ]);

        $newCars = NewCar::query()->forUser($this->restrictedUser)->get();

        // Should see: 2 from d1 + 3 from d2 + 1 from franchise1 with null dealership = 6
        $this->assertCount(6, $newCars);
    }

    public function testUserWithNoAccessSeesNoFranchiseOnlyRecords(): void
    {
        // Create offers with null dealership
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise1->id,
        ]);
        VehicleOffer::factory()->create([
            'dealership_id' => null,
            'franchise_id' => $this->franchise2->id,
        ]);

        $offers = VehicleOffer::query()->forUser($this->noAccessUser)->get();

        $this->assertCount(0, $offers);
    }

    public function testVehicleModelDoesNotUseFranchiseAccess(): void
    {
        // Vehicle model only has dealership_id, not franchise_id in fillable
        // So it should use standard dealership-only filtering

        $vehicles = Vehicle::query()->forUser($this->restrictedUser)->get();

        // Should only see vehicles from assigned dealerships (2 + 3 = 5)
        $this->assertCount(5, $vehicles);
    }
}
