<?php

namespace Tests\Feature\Controllers;

use App\Mail\LiveSiteAccessRequestActionedMail;
use App\Mail\LiveSiteAccessRequestCreatedMail;
use App\Master\Models\LiveSiteAccessRequest;
use Database\Seeders\Global\ApproveLiveSiteAccessPermissionSeed;
use Database\Seeders\Global\GlobalRoleSeed;
use Illuminate\Support\Facades\Mail;
use Mtc\MercuryDataModels\Tenant;
use Mtc\MercuryDataModels\User;
use Tests\DatabaseTestCase;
use Tests\UserForTenant;

class LiveSiteAccessRequestControllerTest extends DatabaseTestCase
{
    use UserForTenant;

    private function makeLiveTenant(): Tenant
    {
        return Tenant::factory()->create(['live_at' => now()]);
    }

    private function makeApprover(): User
    {
        $this->seed(GlobalRoleSeed::class);
        $this->seed(ApproveLiveSiteAccessPermissionSeed::class);

        $approver = User::factory()->create();
        $approver->assignRole('mtc');
        $approver->givePermissionTo('approve-live-site-access');

        return $approver;
    }

    public function testStoreCreatesPendingRequestForLiveTenant(): void
    {
        Mail::fake();
        $tenant = $this->makeLiveTenant();

        $response = $this->actingAs($this->getUser())
            ->postJson(route('live-site-access.store', [], false), [
                'tenant_id' => $tenant->id,
                'reason' => 'Investigating incident #42',
            ]);

        $response->assertStatus(201);

        $this->assertDatabaseHas('live_site_access_requests', [
            'requester_id' => $this->getUser()->id,
            'tenant_id' => $tenant->id,
            'status' => 'pending',
            'reason' => 'Investigating incident #42',
        ]);
    }

    public function testStoreRejectsNonLiveTenant(): void
    {
        $tenant = Tenant::factory()->create(['live_at' => null]);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('live-site-access.store', [], false), [
                'tenant_id' => $tenant->id,
                'reason' => 'just curious',
            ]);

        $response->assertStatus(422);
    }

    public function testStoreRejectsDuplicatePendingRequest(): void
    {
        Mail::fake();
        $tenant = $this->makeLiveTenant();

        LiveSiteAccessRequest::create([
            'requester_id' => $this->getUser()->id,
            'tenant_id' => $tenant->id,
            'reason' => 'first',
            'status' => 'pending',
        ]);

        $response = $this->actingAs($this->getUser())
            ->postJson(route('live-site-access.store', [], false), [
                'tenant_id' => $tenant->id,
                'reason' => 'second',
            ]);

        $response->assertStatus(422);
        $this->assertEquals(1, LiveSiteAccessRequest::query()->count());
    }

    public function testStoreSendsNotificationToApprovers(): void
    {
        Mail::fake();
        $approver = $this->makeApprover();
        $tenant = $this->makeLiveTenant();

        $this->actingAs($this->getUser())
            ->postJson(route('live-site-access.store', [], false), [
                'tenant_id' => $tenant->id,
                'reason' => 'routine work',
            ]);

        Mail::assertSent(
            LiveSiteAccessRequestCreatedMail::class,
            fn ($mail) => $mail->hasTo($approver->email)
        );
    }

    public function testApproveRequiresPermission(): void
    {
        $tenant = $this->makeLiveTenant();
        $requester = User::factory()->create();
        $requester->assignRole('mtc');

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $requester->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'pending',
        ]);

        // acting user is MTC but has no approve-live-site-access permission
        $response = $this->actingAs($this->getUser())
            ->postJson(route('live-site-access.approve', $accessRequest->id, false));

        $response->assertStatus(403);
    }

    public function testApproveBlocksSelfApproval(): void
    {
        Mail::fake();
        $approver = $this->makeApprover();
        $tenant = $this->makeLiveTenant();

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $approver->id,
            'tenant_id' => $tenant->id,
            'reason' => 'self',
            'status' => 'pending',
        ]);

        $response = $this->actingAs($approver)
            ->postJson(route('live-site-access.approve', $accessRequest->id, false));

        $response->assertStatus(422);
    }

    public function testApproveSetsFieldsAndSendsEmail(): void
    {
        Mail::fake();
        $approver = $this->makeApprover();
        $tenant = $this->makeLiveTenant();

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $this->getUser()->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'pending',
        ]);

        $response = $this->actingAs($approver)
            ->postJson(
                route('live-site-access.approve', $accessRequest->id, false),
                ['admin_notes' => 'approved, please use care']
            );

        $response->assertStatus(200);
        $accessRequest->refresh();
        $this->assertEquals('approved', $accessRequest->status);
        $this->assertEquals($approver->id, $accessRequest->actioned_by);
        $this->assertNotNull($accessRequest->actioned_at);
        $this->assertNotNull($accessRequest->expires_at);
        $this->assertTrue($accessRequest->expires_at->between(now()->addMinutes(55), now()->addMinutes(65)));
        $this->assertEquals('approved, please use care', $accessRequest->admin_notes);

        Mail::assertSent(
            LiveSiteAccessRequestActionedMail::class,
            fn ($mail) => $mail->hasTo($this->getUser()->email)
        );
    }

    public function testRejectRequiresNotes(): void
    {
        $approver = $this->makeApprover();
        $tenant = $this->makeLiveTenant();

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $this->getUser()->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'pending',
        ]);

        $response = $this->actingAs($approver)
            ->postJson(route('live-site-access.reject', $accessRequest->id, false));

        $response->assertStatus(422);
    }

    public function testRejectMarksRequestRejected(): void
    {
        Mail::fake();
        $approver = $this->makeApprover();
        $tenant = $this->makeLiveTenant();

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $this->getUser()->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'pending',
        ]);

        $response = $this->actingAs($approver)
            ->postJson(
                route('live-site-access.reject', $accessRequest->id, false),
                ['admin_notes' => 'not justified']
            );

        $response->assertStatus(200);
        $accessRequest->refresh();
        $this->assertEquals('rejected', $accessRequest->status);
        $this->assertEquals($approver->id, $accessRequest->actioned_by);
        $this->assertEquals('not justified', $accessRequest->admin_notes);
    }

    public function testSetSiteBlockedWithoutGrantForLiveTenant(): void
    {
        $tenant = $this->makeLiveTenant();

        $response = $this->actingAs($this->getUser())
            ->postJson(
                route('users.set-site', [], false),
                ['selected_site' => $tenant->id]
            );

        $response->assertStatus(403);
        $this->assertTrue($response->json('requires_access_request'));
        $this->assertEquals($tenant->id, $response->json('tenant_id'));
    }

    public function testSetSiteAllowedWithValidGrantAndConsumesIt(): void
    {
        $tenant = $this->makeLiveTenant();
        $user = $this->getUser();

        $accessRequest = LiveSiteAccessRequest::create([
            'requester_id' => $user->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'approved',
            'actioned_at' => now(),
            'expires_at' => now()->addHour(),
        ]);

        $response = $this->actingAs($user)
            ->postJson(
                route('users.set-site', [], false),
                ['selected_site' => $tenant->id]
            );

        $response->assertStatus(200);
        $accessRequest->refresh();
        $this->assertNotNull($accessRequest->session_id);
        $this->assertNotNull($accessRequest->first_used_at);
        $this->assertNotNull($accessRequest->last_used_at);
    }

    public function testSetSiteSecondCallBumpsLastUsedAt(): void
    {
        $tenant = $this->makeLiveTenant();
        $user = $this->getUser();

        LiveSiteAccessRequest::create([
            'requester_id' => $user->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'approved',
            'actioned_at' => now(),
            'expires_at' => now()->addHour(),
        ]);

        $this->actingAs($user)
            ->postJson(route('users.set-site', [], false), ['selected_site' => $tenant->id])
            ->assertStatus(200);

        $first = LiveSiteAccessRequest::query()->first();
        $firstUsedAt = $first->first_used_at;
        $firstSessionId = $first->session_id;

        sleep(1);

        $this->actingAs($user)
            ->postJson(route('users.set-site', [], false), ['selected_site' => $tenant->id])
            ->assertStatus(200);

        $second = LiveSiteAccessRequest::query()->first();
        $this->assertEquals($firstUsedAt, $second->first_used_at);
        $this->assertEquals($firstSessionId, $second->session_id);
        $this->assertTrue($second->last_used_at->greaterThan($firstUsedAt));
    }

    public function testSetSiteBlockedWithExpiredGrant(): void
    {
        $tenant = $this->makeLiveTenant();
        $user = $this->getUser();

        LiveSiteAccessRequest::create([
            'requester_id' => $user->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'approved',
            'actioned_at' => now()->subHours(2),
            'expires_at' => now()->subHour(),
        ]);

        $response = $this->actingAs($user)
            ->postJson(route('users.set-site', [], false), ['selected_site' => $tenant->id]);

        $response->assertStatus(403);
    }

    public function testSetSiteBlockedWithGrantFromDifferentSession(): void
    {
        $tenant = $this->makeLiveTenant();
        $user = $this->getUser();

        LiveSiteAccessRequest::create([
            'requester_id' => $user->id,
            'tenant_id' => $tenant->id,
            'reason' => 'x',
            'status' => 'approved',
            'actioned_at' => now(),
            'expires_at' => now()->addHour(),
            'session_id' => 'some-other-session',
            'first_used_at' => now()->subMinutes(5),
        ]);

        $response = $this->actingAs($user)
            ->postJson(route('users.set-site', [], false), ['selected_site' => $tenant->id]);

        $response->assertStatus(403);
    }

    public function testSetSiteToNonLiveTenantUnchanged(): void
    {
        $tenant = Tenant::factory()->create(['live_at' => null]);
        $user = $this->getUser();

        $response = $this->actingAs($user)
            ->postJson(route('users.set-site', [], false), ['selected_site' => $tenant->id]);

        $response->assertStatus(200);
        $this->assertEquals(0, LiveSiteAccessRequest::query()->count());
    }
}
