<?php

namespace Tests\Feature\Controllers;

use App\Models\PaymentProvider;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Tests\TestCase;

class PolarWebhookTest extends TestCase
{
    protected User $user;

    protected Plan $plan;

    protected Subscription $subscription;

    protected string $webhookSecret = 'test_webhook_secret';

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

        // Allow any error, warning, and info logs for all tests
        Log::shouldReceive('error')
            ->zeroOrMoreTimes()
            ->withAnyArgs();
        Log::shouldReceive('warning')
            ->zeroOrMoreTimes()
            ->withAnyArgs();
        Log::shouldReceive('info')
            ->zeroOrMoreTimes()
            ->withAnyArgs();

        // Create payment provider configuration in database
        PaymentProvider::factory()->create([
            'name' => 'polar',
            'display_name' => 'Polar.sh',
            'is_active' => true,
            'configuration' => [
                'api_key' => 'test_api_key',
                'webhook_secret' => $this->webhookSecret,
                'sandbox' => false, // Important: disable sandbox for validation tests
                'sandbox_api_key' => 'sandbox_test_api_key',
                'sandbox_webhook_secret' => 'sandbox_test_webhook_secret',
                'access_token' => 'test_access_token',
            ],
        ]);

        // Create test data
        $this->user = User::factory()->create([
            'email' => 'test@example.com',
            'polar_cust_id' => 'cus_test123',
        ]);

        $this->plan = Plan::factory()->create([
            'name' => 'Test Plan',
            'price' => 29.99,
            'monthly_billing' => true,
        ]);

        $this->subscription = Subscription::factory()->create([
            'user_id' => $this->user->id,
            'plan_id' => $this->plan->id,
            'provider' => 'polar',
            'provider_subscription_id' => 'sub_test123',
            'status' => 'active',
        ]);
    }

    /** @test */
    public function it_rejects_webhook_with_invalid_signature(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created');

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => 'invalid_signature',
            'webhook-timestamp' => (string) time(),
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(400);
        // Check that we get some kind of error response (actual format may vary)
        $this->assertStringContainsString('error', $response->getContent());
    }

    /** @test */
    public function it_rejects_webhook_with_missing_timestamp(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created');
        $signature = $this->generatePolarSignature($payload);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(400);
        $response->assertJson(['error' => 'Invalid webhook signature']);
    }

    /** @test */
    public function it_rejects_webhook_with_old_timestamp(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created');
        $oldTimestamp = time() - 600; // 10 minutes ago
        $signature = $this->generatePolarSignature($payload, $oldTimestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $oldTimestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(400);
        $response->assertJson(['error' => 'Invalid webhook signature']);
    }

    /** @test */
    public function it_rejects_webhook_with_future_timestamp(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created');
        $futureTimestamp = time() + 600; // 10 minutes in future
        $signature = $this->generatePolarSignature($payload, $futureTimestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $futureTimestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(400);
        $response->assertJson(['error' => 'Invalid webhook signature']);
    }

    /** @test */
    public function it_processes_valid_webhook_with_correct_signature(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created');
        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
    }

    /** @test */
    public function it_handles_webhook_idempotency(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created', [
            'id' => 'wh_test123',
        ]);
        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        // First request should succeed
        $response1 = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response1->assertStatus(200);
        $response1->assertJson(['status' => 'success']);

        // Second request with same webhook ID should be ignored
        $response2 = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response2->assertStatus(200);
        $response2->assertJson(['status' => 'success', 'message' => 'Webhook already processed']);
    }

    /** @test */
    public function it_handles_subscription_created_webhook(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.created', [
            'data' => [
                'object' => [
                    'id' => 'sub_new123',
                    'customer_id' => 'cus_test123',
                    'product_id' => 'prod_test123',
                    'status' => 'active',
                    'current_period_start' => now()->toISOString(),
                    'current_period_end' => now()->addMonth()->toISOString(),
                    'created_at' => now()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
    }

    /** @test */
    public function it_handles_subscription_active_webhook(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.active', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                    'current_period_start' => now()->toISOString(),
                    'current_period_end' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was updated
        $this->subscription->refresh();
        $this->assertEquals('active', $this->subscription->status);
    }

    /** @test */
    public function it_handles_subscription_cancelled_webhook_with_reason(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.cancelled', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'cancelled',
                    'customer_cancellation_reason' => 'too_expensive',
                    'customer_cancellation_comment' => 'Found a cheaper alternative',
                    'cancelled_at' => now()->toISOString(),
                    'ends_at' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was cancelled with reason
        $this->subscription->refresh();
        $this->assertEquals('cancelled', $this->subscription->status);
        $this->assertEquals('too_expensive', $this->subscription->cancellation_reason);
    }

    /** @test */
    public function it_handles_subscription_paused_webhook(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.paused', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'paused',
                    'paused_at' => now()->toISOString(),
                    'pause_reason' => 'customer_request',
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was paused
        $this->subscription->refresh();
        $this->assertEquals('paused', $this->subscription->status);
    }

    /** @test */
    public function it_handles_subscription_resumed_webhook(): void
    {
        // First pause the subscription
        $this->subscription->update(['status' => 'paused']);

        $payload = $this->createPolarWebhookPayload('subscription.resumed', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                    'resumed_at' => now()->toISOString(),
                    'resume_reason' => 'customer_request',
                    'current_period_start' => now()->toISOString(),
                    'current_period_end' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was resumed
        $this->subscription->refresh();
        $this->assertEquals('active', $this->subscription->status);
    }

    /** @test */
    public function it_handles_subscription_trial_will_end_webhook(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.trial_will_end', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'trialing',
                    'trial_ends_at' => now()->addDays(3)->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify trial end date was updated
        $this->subscription->refresh();
        $this->assertNotNull($this->subscription->trial_ends_at);
    }

    /** @test */
    public function it_handles_subscription_trial_ended_webhook(): void
    {
        $payload = $this->createPolarWebhookPayload('subscription.trial_ended', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                    'trial_ended_at' => now()->toISOString(),
                    'current_period_start' => now()->toISOString(),
                    'current_period_end' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was converted from trial
        $this->subscription->refresh();
        $this->assertEquals('active', $this->subscription->status);
    }

    /** @test */
    public function it_handles_subscription_lookup_by_checkout_id_fallback(): void
    {
        // Create subscription without provider_subscription_id but with checkout_id
        $subscriptionWithoutId = Subscription::factory()->create([
            'user_id' => $this->user->id,
            'plan_id' => $this->plan->id,
            'provider' => 'polar',
            'provider_subscription_id' => null,
            'provider_checkout_id' => 'chk_test123',
            'status' => 'pending',
        ]);

        $payload = $this->createPolarWebhookPayload('subscription.active', [
            'data' => [
                'object' => [
                    'id' => 'sub_new456',
                    'checkout_id' => 'chk_test123', // This should match the subscription
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                    'current_period_start' => now()->toISOString(),
                    'current_period_end' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);

        // Verify subscription was found and updated
        $subscriptionWithoutId->refresh();
        $this->assertEquals('active', $subscriptionWithoutId->status);
        $this->assertEquals('sub_new456', $subscriptionWithoutId->provider_subscription_id);
    }

    /** @test */
    public function it_handles_unknown_webhook_type_gracefully(): void
    {
        $payload = $this->createPolarWebhookPayload('unknown.event', [
            'data' => [
                'object' => [
                    'id' => 'test123',
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
    }

    /** @test */
    public function it_handles_webhook_processing_errors_gracefully(): void
    {
        // Create invalid payload that will cause processing error
        $payload = $this->createPolarWebhookPayload('subscription.active', [
            'data' => [
                'object' => [
                    'id' => $this->subscription->provider_subscription_id,
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                    'current_period_start' => 'invalid-date', // This will cause error
                    'current_period_end' => now()->addMonth()->toISOString(),
                ],
            ],
        ]);

        $timestamp = time();
        $signature = $this->generatePolarSignature($payload, $timestamp);

        $response = $this->postJson('/webhook/polar', $payload, [
            'webhook-signature' => $signature,
            'webhook-timestamp' => (string) $timestamp,
            'webhook-id' => 'wh_test123',
        ]);

        $response->assertStatus(200);
        $response->assertJson(['status' => 'success']);
    }

    /**
     * Create a Polar webhook payload with the given event type and data.
     */
    protected function createPolarWebhookPayload(string $eventType, array $additionalData = []): array
    {
        return array_merge([
            'id' => 'wh_'.uniqid(),
            'type' => $eventType,
            'created_at' => now()->toISOString(),
            'object' => 'webhook_event',
            'data' => [
                'object' => [
                    'id' => 'sub_test123',
                    'customer_id' => 'cus_test123',
                    'status' => 'active',
                ],
            ],
        ], $additionalData);
    }

    /**
     * Generate a Polar webhook signature using Standard Webhooks format.
     */
    protected function generatePolarSignature(array $payload, ?int $timestamp = null): string
    {
        $timestamp = $timestamp ?? time();
        $webhookId = 'wh_test123';
        $payloadJson = json_encode($payload);
        $signedPayload = $webhookId.'.'.$timestamp.'.'.$payloadJson;

        // Use raw secret (as stored in database) and base64 encode for HMAC
        $encodedSecret = base64_encode($this->webhookSecret);
        $hexHash = hash_hmac('sha256', $signedPayload, $encodedSecret);
        $signature = base64_encode(pack('H*', $hexHash));

        return 'v1,'.$signature;
    }
}
