<?php
declare(strict_types=1);

namespace Tests\Unit\Isolation;

use Tests\TestCase;
use App\Core\App;
use App\Core\ModuleRegistry;
use App\Core\Contracts\PaymentProcessorInterface;
use App\Core\Contracts\RuleEngineInterface;
use App\Core\Contracts\StockManagerInterface;
use App\Core\Contracts\WorkflowEngineInterface;
use App\Core\Facades\Payment;
use App\Core\Facades\Rules;
use App\Core\Facades\Stock;
use App\Core\Facades\Workflow;
use App\Core\DTO\PaymentRequest;
use App\Core\DTO\PaymentResult;
use App\Core\DTO\StockMovementRequest;
use App\Core\DTO\StockMovementResult;
use App\Core\DTO\JournalEntryRequest;
use App\Modules\Rules\RuleEngineAdapter;
use App\Modules\Payments\PaymentProcessorAdapter;
use App\Modules\Inventory\StockManagerAdapter;
use App\Modules\Workflow\WorkflowEngineAdapter;

final class CrossModuleTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $app = App::getInstance();
        $app->singleton(RuleEngineInterface::class, fn() => new RuleEngineAdapter());
        $app->singleton(PaymentProcessorInterface::class, fn() => new PaymentProcessorAdapter());
        $app->singleton(StockManagerInterface::class, fn() => new StockManagerAdapter());
        $app->singleton(WorkflowEngineInterface::class, fn() => new WorkflowEngineAdapter());
    }

    public function testModuleRegistryTracksModules(): void
    {
        ModuleRegistry::register('test_module', [
            'services' => ['TestInterface'],
            'description' => 'Test module',
        ]);

        $this->assertTrue(ModuleRegistry::has('test_module'));
        $this->assertEquals(['TestInterface'], ModuleRegistry::provides('test_module')['services']);
    }

    public function testPaymentRequestDto(): void
    {
        $request = new PaymentRequest(
            memberId: 5,
            amount: '527.00',
            paymentType: 'annual_subscription',
            paymentMethod: 'cash',
        );

        $array = $request->toArray();
        $this->assertEquals(5, $array['member_id']);
        $this->assertEquals('527.00', $array['amount']);
        $this->assertEquals('annual_subscription', $array['payment_type']);
        $this->assertEquals('cash', $array['payment_method']);
        $this->assertArrayNotHasKey('check_number', $array);
    }

    public function testPaymentRequestDtoWithOptionalFields(): void
    {
        $request = new PaymentRequest(
            memberId: 5,
            amount: '10000.00',
            paymentType: 'membership_fee',
            paymentMethod: 'check',
            checkNumber: 'CHK-999',
            checkBank: 'بنك مصر',
        );

        $array = $request->toArray();
        $this->assertEquals('CHK-999', $array['check_number']);
        $this->assertEquals('بنك مصر', $array['check_bank']);
    }

    public function testPaymentResultSuccess(): void
    {
        $result = PaymentResult::ok(42, 100, 'REC-2026-001', '527.00');

        $this->assertTrue($result->success);
        $this->assertEquals(42, $result->paymentId);
        $this->assertEquals(100, $result->receiptId);
        $this->assertEquals('REC-2026-001', $result->receiptNumber);
        $this->assertEquals('527.00', $result->amount);
        $this->assertNull($result->error);
    }

    public function testPaymentResultFailure(): void
    {
        $result = PaymentResult::fail('المبلغ يجب أن يكون أكبر من صفر');

        $this->assertFalse($result->success);
        $this->assertNull($result->paymentId);
        $this->assertEquals('المبلغ يجب أن يكون أكبر من صفر', $result->error);
    }

    public function testPaymentResultFromArray(): void
    {
        $successResult = PaymentResult::fromArray([
            'success' => true,
            'payment_id' => 10,
            'receipt_id' => 20,
            'receipt_number' => 'REC-001',
            'amount' => '100.00',
        ]);
        $this->assertTrue($successResult->success);
        $this->assertEquals(10, $successResult->paymentId);

        $failResult = PaymentResult::fromArray([
            'success' => false,
            'error' => 'some error',
        ]);
        $this->assertFalse($failResult->success);
        $this->assertEquals('some error', $failResult->error);
    }

    public function testStockMovementRequestDto(): void
    {
        $request = new StockMovementRequest(
            itemId: 1,
            warehouseId: 2,
            movementType: 'purchase',
            direction: 'in',
            quantity: '50.00',
            unitCost: '25.00',
            referenceType: 'purchase_order',
            referenceId: 33,
        );

        $array = $request->toArray();
        $this->assertEquals(1, $array['item_id']);
        $this->assertEquals(2, $array['warehouse_id']);
        $this->assertEquals('purchase', $array['movement_type']);
        $this->assertEquals('in', $array['direction']);
        $this->assertEquals('50.00', $array['quantity']);
        $this->assertEquals('25.00', $array['unit_cost']);
        $this->assertArrayNotHasKey('notes', $array);
    }

    public function testStockMovementResultDto(): void
    {
        $ok = StockMovementResult::ok(77);
        $this->assertTrue($ok->success);
        $this->assertEquals(77, $ok->movementId);

        $fail = StockMovementResult::fail('Insufficient stock');
        $this->assertFalse($fail->success);
        $this->assertEquals('Insufficient stock', $fail->error);
    }

    public function testJournalEntryRequestDto(): void
    {
        $request = new JournalEntryRequest(
            entryDate: '2026-05-13',
            descriptionAr: 'تحصيل اشتراك سنوي',
            lines: [
                ['account_id' => 1, 'debit' => '527.00', 'credit' => '0.00'],
                ['account_id' => 2, 'debit' => '0.00', 'credit' => '527.00'],
            ],
            sourceModule: 'payments',
            isAutoGenerated: true,
            autoPost: true,
        );

        $header = $request->toHeaderArray();
        $this->assertEquals('2026-05-13', $header['entry_date']);
        $this->assertEquals('تحصيل اشتراك سنوي', $header['description_ar']);
        $this->assertEquals('payments', $header['source_module']);
        $this->assertEquals(1, $header['is_auto_generated']);

        $lines = $request->toLines();
        $this->assertCount(2, $lines);
        $this->assertEquals('527.00', $lines[0]['debit']);
    }

    public function testFacadesResolveCorrectTypes(): void
    {
        $this->assertInstanceOf(RuleEngineInterface::class, Rules::instance());
        $this->assertInstanceOf(PaymentProcessorInterface::class, Payment::instance());
        $this->assertInstanceOf(StockManagerInterface::class, Stock::instance());
        $this->assertInstanceOf(WorkflowEngineInterface::class, Workflow::instance());
    }

    public function testMockCanReplaceRealService(): void
    {
        $mock = new class implements PaymentProcessorInterface {
            public function processPayment(array $data): array { return ['success' => true, 'payment_id' => 999, 'receipt_id' => 888, 'receipt_number' => 'MOCK-001', 'amount' => $data['amount']]; }
            public function voidPayment(int $paymentId, string $reason): array { return ['success' => true]; }
            public function hasPaid(int $memberId, string $paymentType, ?string $relatedEntityType = null, ?int $relatedEntityId = null): bool { return true; }
            public function totalPaid(int $memberId, ?string $paymentType = null): string { return '9999.00'; }
            public function generateReceiptNumber(): string { return 'MOCK-REC'; }
            public function getPaymentTypeLabel(string $type): string { return 'Mock Label'; }
        };

        App::getInstance()->bind(PaymentProcessorInterface::class, $mock);

        $result = Payment::processPayment(['amount' => '100.00']);
        $this->assertEquals(999, $result['payment_id']);
        $this->assertEquals('100.00', $result['amount']);
    }
}
