<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\Mail\VehiclesInVideoVideoNotFoundMail;
use App\Models\ExternalApiToken;
use App\Modules\VehiclesInVideo\Services\TokenService;
use App\Modules\VehiclesInVideo\Services\VehiclesInVideoApiService;
use App\Modules\VehiclesInVideo\Services\VehiclesInVideoService;
use Database\Seeders\Tenant\VehiclesInVideoSettingSeeder;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Response;
use Illuminate\Support\Facades\Mail;
use Mtc\Crm\Facades\Enquiries;
use Mtc\Crm\Models\Enquiry;
use Mtc\Crm\Models\EnquiryAction;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Vehicle;
use Tests\TenantTestCase;

class SendToVehiclesInVideoTest extends TenantTestCase
{
    private array $requestHistory = [];

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(VehiclesInVideoSettingSeeder::class);
        Settings::update('vehicles-in-video-enabled', true);
        Settings::update('vehicles-in-video-api-key', 'test-api-key');
        Settings::update('vehicles-in-video-client-id', 'test-client');
    }

    public function testEnabledIsFalseWhenSettingDisabled(): void
    {
        Settings::update('vehicles-in-video-enabled', false);
        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $this->assertFalse($handler->enabled());
    }

    public function testEnabledIsTrueWhenSettingEnabled(): void
    {
        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $this->assertTrue($handler->enabled());
    }

    public function testHandleSendsVideoSuccessfully(): void
    {
        $this->bindMockApi([new Response(200, [], json_encode(['result' => 'ok']))]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertTrue($result);
        $this->assertCount(1, $this->requestHistory);
        $this->assertEquals('POST', $this->requestHistory[0]['request']->getMethod());
        $this->assertStringContainsString('/videos/send', (string) $this->requestHistory[0]['request']->getUri());

        $notification = ApiNotification::query()
            ->where('provider', 'vehicles-in-video')
            ->where('reference', 'AB12 CDE')
            ->first();
        $this->assertNotNull($notification);
        $this->assertEquals(VehiclesInVideoService::STATUS_SENT, $notification->data['status']);
        $this->assertEquals($enquiry->id, $notification->data['enquiry_id']);
    }

    public function testHandleRecordsNotFoundOn404(): void
    {
        $this->bindMockApi([new Response(404, [], json_encode(['error' => ['message' => 'no video']]))]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertFalse($result);
        $notification = ApiNotification::query()
            ->where('provider', 'vehicles-in-video')
            ->where('reference', 'AB12 CDE')
            ->first();
        $this->assertNotNull($notification);
        $this->assertEquals(VehiclesInVideoService::STATUS_NOT_FOUND, $notification->data['status']);
    }

    public function testHandleRecordsFailedOnApiError(): void
    {
        $this->bindMockApi([new Response(500, [], json_encode(['error' => ['message' => 'server error']]))]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertFalse($result);
        $notification = ApiNotification::query()
            ->where('provider', 'vehicles-in-video')
            ->where('reference', 'AB12 CDE')
            ->first();
        $this->assertNotNull($notification);
        $this->assertEquals(VehiclesInVideoService::STATUS_FAILED, $notification->data['status']);
    }

    public function testHandleReturnsFalseWhenEnquiryHasNoVehicle(): void
    {
        $this->bindMockApi([]);
        $this->seedActiveToken();

        $enquiry = Enquiry::factory()->create([
            'email' => 'noone@example.com',
            'reason_type' => null,
            'reason_id' => null,
        ]);

        $action = EnquiryAction::factory()->create([
            'action_name' => 'send-to-vehicles-in-video',
            'enquiry_id' => $enquiry->id,
            'processed_at' => null,
            'failed_at' => null,
        ]);

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertFalse($result);
        $this->assertEquals(0, ApiNotification::where('provider', 'vehicles-in-video')->count());
    }

    public function testHandleReturnsNullForExpiredAction(): void
    {
        $this->bindMockApi([]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle(processedAt: now());

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertNull($result);
        $this->assertEquals(0, ApiNotification::where('provider', 'vehicles-in-video')->count());
    }

    public function testHandleReturnsFalseWhenNoTokenAvailable(): void
    {
        $this->bindMockApi([]);

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertFalse($result);
        $this->assertCount(0, $this->requestHistory);
        $this->assertEquals(0, ApiNotification::where('provider', 'vehicles-in-video')->count());
    }

    public function testHandleSendsNotFoundMailOn404(): void
    {
        Mail::fake();
        Settings::update('vehicles-in-video-notification-recipients', 'ops@example.com');
        $this->bindMockApi([new Response(404, [], json_encode(['error' => ['message' => 'no video']]))]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $handler->handle($action, $enquiry);

        Mail::assertSent(
            VehiclesInVideoVideoNotFoundMail::class,
            fn ($mail) => $mail->hasTo('ops@example.com')
        );
    }

    public function testHandleCapturesRequestAndCustomerPayloadOn200(): void
    {
        $this->bindMockApi([new Response(200, [], json_encode(['result' => 'ok']))]);
        $this->seedActiveToken();

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $handler->handle($action, $enquiry);

        $notification = ApiNotification::query()
            ->where('provider', 'vehicles-in-video')
            ->where('reference', 'AB12 CDE')
            ->first();
        $this->assertNotNull($notification);
        $this->assertEquals('AB12 CDE', $notification->data['request']['reference']);
        $this->assertEquals('customer@example.com', $notification->data['request']['customer_email']);
        $this->assertTrue($notification->data['request']['instant_send']);
        $this->assertEquals((string) $enquiry->id, $notification->data['request']['meta']['leadId']);
        $this->assertEquals('Customer', $notification->data['customer']['name']);
        $this->assertEquals('customer@example.com', $notification->data['customer']['email']);
        $this->assertNull($notification->data['customer']['phone']);
    }

    public function testHandleAutoRefreshesExpiredAuthToken(): void
    {
        $this->bindMockApi([
            new Response(200, [], json_encode([
                'auth_token' => 'refreshed-auth',
                'refresh_token' => 'refreshed-refresh',
                'auth_token_expiry_secs' => 3600,
                'refresh_token_expiry_secs' => 86400,
            ])),
            new Response(200, [], json_encode(['result' => 'ok'])),
        ]);

        ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'auth_token' => 'stale-auth',
            'refresh_token' => 'old-refresh',
            'auth_expires_at' => now()->subMinutes(30),
            'refresh_expires_at' => now()->addDays(180),
        ]);

        [$action, $enquiry] = $this->makeEnquiryWithVehicle();

        $handler = Enquiries::makeAction('send-to-vehicles-in-video');
        $result = $handler->handle($action, $enquiry);

        $this->assertTrue($result);
        $this->assertCount(2, $this->requestHistory);
        $this->assertStringContainsString(
            '/auth/tokens/refresh',
            (string) $this->requestHistory[0]['request']->getUri()
        );
        $this->assertStringContainsString(
            '/videos/send',
            (string) $this->requestHistory[1]['request']->getUri()
        );
        $this->assertEquals(
            'Bearer refreshed-auth',
            $this->requestHistory[1]['request']->getHeaderLine('Authorization')
        );

        $token = ExternalApiToken::latest()->first();
        $this->assertEquals('refreshed-auth', $token->auth_token);
        $this->assertEquals('refreshed-refresh', $token->refresh_token);
    }

    private function bindMockApi(array $responses): void
    {
        $mock = new MockHandler($responses);
        $stack = HandlerStack::create($mock);
        $stack->push(Middleware::history($this->requestHistory));
        $client = new Client(['handler' => $stack, 'http_errors' => false]);

        $this->app->bind(
            VehiclesInVideoApiService::class,
            fn () => new VehiclesInVideoApiService($client)
        );
    }

    private function seedActiveToken(): ExternalApiToken
    {
        return ExternalApiToken::create([
            'provider' => TokenService::PROVIDER,
            'auth_token' => 'valid-auth-token',
            'refresh_token' => 'valid-refresh-token',
            'auth_expires_at' => now()->addHour(),
            'refresh_expires_at' => now()->addDays(180),
        ]);
    }

    private function makeEnquiryWithVehicle(?\DateTimeInterface $processedAt = null): array
    {
        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB12 CDE',
        ]);

        $enquiry = Enquiry::factory()->create([
            'email' => 'customer@example.com',
            'reason_type' => $vehicle->getMorphClass(),
            'reason_id' => $vehicle->id,
        ]);

        $action = EnquiryAction::factory()->create([
            'action_name' => 'send-to-vehicles-in-video',
            'enquiry_id' => $enquiry->id,
            'processed_at' => $processedAt,
            'failed_at' => null,
        ]);

        return [$action, $enquiry];
    }
}
