<?php

namespace Tests\Feature\Integration;

use App\Domains\Attachable\Enums\AttachmentType;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\Integration\Models\ClientIntegration;
use App\Domains\Integration\Models\Integration;
use App\Domains\Integration\Services\TelepagosIntegrationService;
use App\Domains\Tenant\Models\Tenant;
use Exception;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Tests\TestCase;

class IntegrationServiceTest extends TestCase
{
    use RefreshDatabase;

    private Tenant $tenant;

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

        // Set the integrations secret for tests
        config(['services.integrations.secret' => 'base64:'.base64_encode(random_bytes(32))]);

        // Clear cache to prevent test pollution
        Cache::flush();

        $hdrKey = (string) Str::uuid();
        $ftrKey = (string) Str::uuid();

        $headerAttachment = Attachment::create([
            'key' => $hdrKey,
            'path' => 'tenants/'.$hdrKey.'.png',
            'filename' => 'logo_header.png',
            'type' => AttachmentType::Image,
            'mime_type' => 'image/png',
        ]);
        $footerAttachment = Attachment::create([
            'key' => $ftrKey,
            'path' => 'tenants/'.$ftrKey.'.png',
            'filename' => 'logo_footer.png',
            'type' => AttachmentType::Image,
            'mime_type' => 'image/png',
        ]);

        // Create a test tenant
        $this->tenant = Tenant::create([
            'codigo' => 'test-tenant',
            'nombre' => 'Test Tenant',
            'dominio' => 'test.com',
            'primary_color' => '#ffffff',
            'secondary_color' => '#ffffff',
            'danger_color' => '#ffffff',
            'success_color' => '#ffffff',
            'header_bg_color' => '#ffffff',
            'footer_bg_color' => '#ffffff',
            'header_logo_id' => $headerAttachment->id,
            'footer_logo_id' => $footerAttachment->id,
        ]);
    }

    public function test_it_throws_exception_if_integration_code_is_invalid(): void
    {
        $service = new TelepagosIntegrationService('invalid_code');

        $this->expectException(Exception::class);
        $this->expectExceptionMessage("Integration with code 'invalid_code' not found.");

        $service->forTenant($this->tenant->codigo);
    }

    public function test_it_throws_exception_if_client_integration_is_not_configured(): void
    {
        // Seed integration but don't configure for tenant
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        $service = new TelepagosIntegrationService('telepagos');

        $this->expectException(Exception::class);
        $this->expectExceptionMessage("Client 'test-tenant' does not have integration 'telepagos_homo' configured.");

        $service->forTenant($this->tenant->codigo);
    }

    public function test_it_resolves_base_url_and_paths(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar/', // Trailing slash to test trimming
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'user123',
                'password' => 'pass123',
            ],
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $this->assertEquals('https://api.telepagos.com.ar', $service->getUrl());
        $this->assertEquals('https://api.telepagos.com.ar/v1/payments', $service->getUrl('v1/payments'));
        $this->assertEquals('https://api.telepagos.com.ar/v1/payments', $service->getUrl('/v1/payments'));
    }

    public function test_it_generates_correct_headers_for_telepagos_using_cached_token(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        // Mock HTTP response sequence for authentication
        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::sequence()
                ->push([
                    'status' => 'ok',
                    'token' => 'mock-jwt-token-123',
                    'expires_at' => now()->addHour()->toDateTimeString(),
                ], 200)
                ->push([
                    'status' => 'ok',
                    'token' => 'new-mock-jwt-token',
                    'expires_at' => now()->addHour()->toDateTimeString(),
                ], 200),
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        // Fetch headers first time (triggers API login)
        $headers = $service->getHeaders();

        $this->assertEquals('Bearer mock-jwt-token-123', $headers['Authorization']);
        $this->assertEquals('application/json', $headers['Content-Type']);
        $this->assertEquals('application/json', $headers['Accept']);

        // Assert HTTP call was made once
        Http::assertSentCount(1);

        // Retrieve token again, should be same (cached)
        $this->assertEquals('mock-jwt-token-123', $service->getToken());
        Http::assertSentCount(1); // Still 1 since it's cached!

        // Clear token, should trigger another API login (sequence returns second token)
        $service->clearToken();
        $this->assertEquals('new-mock-jwt-token', $service->getToken());
        Http::assertSentCount(2);
    }

    public function test_tenants_from_the_same_client_share_configuration_and_cached_token(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'shared-user',
                'password' => 'shared-password',
            ],
        ]);

        $secondTenant = $this->tenant->replicate()->fill([
            'codigo' => 'second-tenant',
            'nombre' => 'Second Tenant',
            'dominio' => 'second.test.com',
        ]);
        $secondTenant->save();

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'shared-token',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ]),
        ]);

        $firstService = (new TelepagosIntegrationService('telepagos'))->forTenant($this->tenant->codigo);
        $secondService = (new TelepagosIntegrationService('telepagos'))->forTenant($secondTenant->codigo);

        $this->assertSame('shared-token', $firstService->getToken());
        $this->assertSame('shared-token', $secondService->getToken());
        Http::assertSentCount(1);
    }

    public function test_it_throws_exception_if_credentials_are_missing(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => '',
                'password' => 'tele_pass',
            ],
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $this->expectException(Exception::class);
        $this->expectExceptionMessage('Missing username or password in Telepagos integration settings.');

        $service->getToken();
    }

    public function test_it_throws_exception_if_api_fails(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'error',
                'message' => 'Invalid credentials',
                'token' => 'must-not-be-logged',
            ], 401),
        ]);

        Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
        Log::shouldReceive('error')
            ->once()
            ->with('Telepagos authentication failed: Invalid credentials', \Mockery::on(function ($context) {
                return ! array_key_exists('username', $context)
                    && $context['response_status'] === 401
                    && $context['response_body'] === [
                        'status' => 'error',
                        'message' => 'Invalid credentials',
                        'token' => '[REDACTED]',
                    ];
            }));

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $this->expectException(Exception::class);
        $this->expectExceptionMessage('Telepagos authentication failed: Invalid credentials');

        $service->getToken();
    }

    public function test_it_returns_preconfigured_client(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'mock-jwt-token-123',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ], 200),
            'https://api.telepagos.com.ar/v1/payments' => Http::response([
                'status' => 'success',
                'payment_id' => 999,
            ], 200),
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        // Get configured client and perform GET request
        $client = $service->client();
        $this->assertInstanceOf(PendingRequest::class, $client);

        $response = $client->get('/v1/payments');

        $this->assertTrue($response->successful());
        $this->assertEquals(999, $response->json('payment_id'));

        // Assert the authorization header was correctly set during request
        Http::assertSent(function ($request) {
            return $request->hasHeader('Authorization', 'Bearer mock-jwt-token-123')
                && $request->url() === 'https://api.telepagos.com.ar/v1/payments';
        });
    }

    public function test_it_generates_qr_code(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'mock-jwt-token-123',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ], 200),
            'https://api.telepagos.com.ar/v2/payment/cashin/qr/generate' => Http::response([
                'status' => 'ok',
                'qr_code' => 'mock-qr-code-data',
                'qr_order_id' => 6353,
            ], 200),
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $result = $service->generateQr(1200.00, 'Test Concept', 'Test Description');

        $this->assertEquals('ok', $result['status']);
        $this->assertEquals('mock-qr-code-data', $result['qr_code']);
        $this->assertEquals(6353, $result['qr_order_id']);

        Http::assertSent(function ($request) {
            return $request->hasHeader('Authorization', 'Bearer mock-jwt-token-123')
                && $request->url() === 'https://api.telepagos.com.ar/v2/payment/cashin/qr/generate'
                && $request['amount'] === 1200.00
                && $request['concept'] === 'Test Concept'
                && $request['description'] === 'Test Description';
        });
    }

    public function test_it_logs_and_throws_exception_on_generate_qr_error(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'mock-jwt-token-123',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ], 200),
            'https://api.telepagos.com.ar/v2/payment/cashin/qr/generate' => Http::response([
                'status' => 'error',
                'message' => 'Importe inválido',
            ], 422),
        ]);

        Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
        Log::shouldReceive('error')
            ->once()
            ->with('Telepagos QR generation failed: Importe inválido', \Mockery::on(function ($context) {
                return $context['amount'] === 1200.00
                    && $context['concept'] === 'Test Concept'
                    && $context['description'] === 'Test Description'
                    && $context['response_status'] === 422
                    && $context['response_body'] === ['status' => 'error', 'message' => 'Importe inválido'];
            }));

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $this->expectException(Exception::class);
        $this->expectExceptionMessage('Telepagos QR generation failed: Importe inválido');

        $service->generateQr(1200.00, 'Test Concept', 'Test Description');
    }

    public function test_it_gets_cashin_details(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'mock-jwt-token-123',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ], 200),
            'https://api.telepagos.com.ar/v2/payment/cashin/6351' => Http::response([
                'status' => 'ok',
                'buyer' => [
                    'cuit' => '20416561398',
                    'cvu' => '0000124900000000011974',
                ],
                'amount' => 1200,
                'concept' => 'VAR',
                'operation' => 'QR Telepagos',
                'operation_id' => 37,
                'description' => 'Pago prueba',
                'transaction_id' => '2026070364',
                'qr_order_id' => 6351,
                'link_id' => null,
            ], 200),
        ]);

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $result = $service->getCashinDetails(6351);

        $this->assertEquals('ok', $result['status']);
        $this->assertEquals(1200, $result['amount']);
        $this->assertEquals('VAR', $result['concept']);
        $this->assertEquals(6351, $result['qr_order_id']);

        Http::assertSent(function ($request) {
            return $request->hasHeader('Authorization', 'Bearer mock-jwt-token-123')
                && $request->url() === 'https://api.telepagos.com.ar/v2/payment/cashin/6351'
                && $request->method() === 'GET';
        });
    }

    public function test_it_logs_and_throws_exception_on_get_cashin_details_error(): void
    {
        Integration::create([
            'integration_code' => 'telepagos_homo',
            'name' => 'Telepagos',
            'url' => 'https://api.telepagos.com.ar',
            'integration_data_schema' => [
                'username' => 'required|string',
                'password' => 'required|string',
            ],
        ]);

        ClientIntegration::create([
            'client_id' => $this->tenant->client_id,
            'integration_code' => 'telepagos_homo',
            'integration_data' => [
                'username' => 'tele_user',
                'password' => 'tele_pass',
            ],
        ]);

        Http::fake([
            'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
                'status' => 'ok',
                'token' => 'mock-jwt-token-123',
                'expires_at' => now()->addHour()->toDateTimeString(),
            ], 200),
            'https://api.telepagos.com.ar/v2/payment/cashin/6351' => Http::response([
                'status' => 'error',
                'message' => 'Cashin no encontrado',
            ], 404),
        ]);

        Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
        Log::shouldReceive('error')
            ->once()
            ->with('Telepagos get cash-in details failed: Cashin no encontrado', \Mockery::on(function ($context) {
                return $context['cashin_id'] === 6351
                    && $context['response_status'] === 404
                    && $context['response_body'] === ['status' => 'error', 'message' => 'Cashin no encontrado'];
            }));

        $service = new TelepagosIntegrationService('telepagos');
        $service->forTenant($this->tenant->codigo);

        $this->expectException(Exception::class);
        $this->expectExceptionMessage('Telepagos get cash-in details failed: Cashin no encontrado');

        $service->getCashinDetails(6351);
    }
}
