<?php

namespace Tests\Feature\Modules;

use Tests\TestCase;
use App\Modules\PaymentModule;
use App\Services\PaymentService;
use App\Services\Core\ModuleManager;
use App\Services\Core\EventBus;
use App\Services\Core\ConfigurationManager;
use App\DTOs\TelegramUpdateDTO;
use App\Models\User;
use App\Models\Payment;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;

class PaymentModuleTest extends TestCase
{
    use RefreshDatabase;

    private PaymentModule $module;
    private $mockModuleManager;
    private $mockEventBus;
    private $mockConfigManager;
    private $mockPaymentService;

    protected function setUp(): void
    {
        parent::setUp();
        
        $this->mockModuleManager = Mockery::mock(ModuleManager::class);
        $this->mockEventBus = Mockery::mock(EventBus::class);
        $this->mockConfigManager = Mockery::mock(ConfigurationManager::class);
        $this->mockPaymentService = Mockery::mock(PaymentService::class);
        
        $this->module = new PaymentModule(
            $this->mockModuleManager,
            $this->mockEventBus,
            $this->mockConfigManager,
            $this->mockPaymentService
        );
    }

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    public function test_module_registration()
    {
        $this->assertEquals('PaymentModule', $this->module->getName());
        $this->assertEquals('1.0.0', $this->module->getVersion());
        $this->assertNotEmpty($this->module->getDescription());
        $this->assertContains('/payment', $this->module->getHandledCommands());
        $this->assertContains('/wallet', $this->module->getHandledCommands());
    }

    public function test_handles_payment_command()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $updateData = [
            'message' => [
                'text' => '/payment',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.send_message', Mockery::any())
            ->once();

        $result = $this->module->handleUpdate($update);
        $this->assertTrue($result);
    }

    public function test_handles_wallet_command()
    {
        $user = User::factory()->create(['telegram_id' => 67890, 'balance' => 50000]);
        
        $updateData = [
            'message' => [
                'text' => '/wallet',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.send_message', Mockery::any())
            ->once();

        $result = $this->module->handleUpdate($update);
        $this->assertTrue($result);
    }

    public function test_handles_payment_amount_callback()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $updateData = [
            'callback_query' => [
                'data' => 'payment_10000',
                'message' => [
                    'chat' => ['id' => 12345],
                    'message_id' => 123
                ],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockPaymentService->shouldReceive('createPayment')
            ->with($user, 10000, 'Wallet top-up')
            ->andReturn([
                'authority' => 'test_authority',
                'payment_url' => 'https://zarinpal.com/test'
            ]);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.edit_message', Mockery::any())
            ->once();

        $result = $this->module->handleCallback($update);
        $this->assertTrue($result);
    }

    public function test_handles_custom_amount_callback()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $updateData = [
            'callback_query' => [
                'data' => 'payment_custom',
                'message' => [
                    'chat' => ['id' => 12345],
                    'message_id' => 123
                ],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.edit_message', Mockery::any())
            ->once();

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('user.wait_for_input', Mockery::any())
            ->once();

        $result = $this->module->handleCallback($update);
        $this->assertTrue($result);
    }

    public function test_handles_payment_verification()
    {
        $user = User::factory()->create(['telegram_id' => 67890, 'balance' => 0]);
        $payment = Payment::factory()->create([
            'user_id' => $user->id,
            'amount' => 10000,
            'authority' => 'test_authority',
            'status' => 'pending'
        ]);

        $this->mockPaymentService->shouldReceive('verifyPayment')
            ->with('test_authority')
            ->andReturn([
                'success' => true,
                'ref_id' => 'test_ref_id',
                'payment' => $payment
            ]);

        $result = $this->module->verifyPayment('test_authority');
        
        $this->assertTrue($result['success']);
        $this->assertEquals('test_ref_id', $result['ref_id']);
    }

    public function test_handles_failed_payment_verification()
    {
        $this->mockPaymentService->shouldReceive('verifyPayment')
            ->with('invalid_authority')
            ->andReturn([
                'success' => false,
                'error' => 'Invalid authority'
            ]);

        $result = $this->module->verifyPayment('invalid_authority');
        
        $this->assertFalse($result['success']);
        $this->assertEquals('Invalid authority', $result['error']);
    }

    public function test_custom_amount_processing()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $updateData = [
            'message' => [
                'text' => '25000',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        // Mock that user is waiting for payment amount input
        $this->mockConfigManager->shouldReceive('get')
            ->with('user.67890.waiting_for', null)
            ->andReturn('payment_amount');

        $this->mockConfigManager->shouldReceive('forget')
            ->with('user.67890.waiting_for')
            ->once();

        $this->mockPaymentService->shouldReceive('createPayment')
            ->with($user, 25000, 'Wallet top-up')
            ->andReturn([
                'authority' => 'test_authority',
                'payment_url' => 'https://zarinpal.com/test'
            ]);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.send_message', Mockery::any())
            ->once();

        $result = $this->module->handleUpdate($update);
        $this->assertTrue($result);
    }

    public function test_invalid_amount_handling()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $updateData = [
            'message' => [
                'text' => 'invalid_amount',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockConfigManager->shouldReceive('get')
            ->with('user.67890.waiting_for', null)
            ->andReturn('payment_amount');

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.send_message', Mockery::any())
            ->once();

        $result = $this->module->handleUpdate($update);
        $this->assertTrue($result);
    }

    public function test_payment_history_callback()
    {
        $user = User::factory()->create(['telegram_id' => 67890]);
        Payment::factory()->count(3)->create(['user_id' => $user->id]);
        
        $updateData = [
            'callback_query' => [
                'data' => 'payment_history',
                'message' => [
                    'chat' => ['id' => 12345],
                    'message_id' => 123
                ],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];

        $update = TelegramUpdateDTO::fromArray($updateData);

        $this->mockEventBus->shouldReceive('dispatch')
            ->with('telegram.edit_message', Mockery::any())
            ->once();

        $result = $this->module->handleCallback($update);
        $this->assertTrue($result);
    }

    public function test_module_initialization()
    {
        $this->mockEventBus->shouldReceive('listen')
            ->with('payment.verified', Mockery::any())
            ->once();

        $this->mockEventBus->shouldReceive('listen')
            ->with('payment.failed', Mockery::any())
            ->once();

        $result = $this->module->initialize();
        $this->assertTrue($result);
    }

    public function test_zarinpal_configuration()
    {
        $this->mockConfigManager->shouldReceive('get')
            ->with('payments.zarinpal.merchant_id')
            ->andReturn('test-merchant-id');

        $this->mockConfigManager->shouldReceive('get')
            ->with('payments.zarinpal.sandbox', false)
            ->andReturn(true);

        $config = $this->module->getZarinpalConfig();
        
        $this->assertEquals('test-merchant-id', $config['merchant_id']);
        $this->assertTrue($config['sandbox']);
    }

    public function test_payment_limits()
    {
        $this->mockConfigManager->shouldReceive('get')
            ->with('payments.min_amount', 1000)
            ->andReturn(1000);

        $this->mockConfigManager->shouldReceive('get')
            ->with('payments.max_amount', 500000)
            ->andReturn(500000);

        $this->assertTrue($this->module->isValidAmount(10000));
        $this->assertFalse($this->module->isValidAmount(500));
        $this->assertFalse($this->module->isValidAmount(600000));
    }
}