<?php

namespace Tests\Feature\Integration;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Modules\AccountingCore\App\Models\BasicTransaction;
use Modules\AccountingCore\App\Models\BasicTransactionCategory;
use Modules\AccountingCore\App\Services\AccountingSyncService;
use Modules\SystemCore\App\Events\PurchaseOrderPaymentStatusChanged;
use Modules\SystemCore\App\Events\SalesOrderPaymentStatusChanged;
use Modules\SystemCore\App\Models\CorePurchaseOrder;
use Modules\SystemCore\App\Models\CoreSalesOrder;
use Modules\SystemCore\App\Models\PostingLog;
use Tests\TestCase;
use Tests\Traits\CreatesSystemCoreEntities;
use Tests\Traits\CreatesUsers;

class SystemAccountingIntegrationTest extends TestCase
{
    use CreatesSystemCoreEntities;
    use CreatesUsers;
    use RefreshDatabase;

    protected AccountingSyncService $syncService;

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

        if (! moduleExists('AccountingCore')) {
            $this->markTestSkipped('AccountingCore module is not enabled');
        }

        $this->syncService = app(AccountingSyncService::class);
    }

    // ==========================================
    // Sales Order Sync Tests
    // ==========================================

    public function test_sync_sales_order_creates_income_transaction(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder([
            'total_amount' => 1500.00,
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertNotNull($transaction);
        $this->assertInstanceOf(BasicTransaction::class, $transaction);
        $this->assertEquals('income', $transaction->type);
        $this->assertEquals(1500.00, $transaction->amount);
        $this->assertEquals(CoreSalesOrder::class, $transaction->sourceable_type);
        $this->assertEquals($salesOrder->id, $transaction->sourceable_id);
    }

    public function test_sync_sales_order_only_syncs_when_paid(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $pendingOrder = $this->createCoreSalesOrder([
            'payment_status' => 'pending',
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncSalesOrder($pendingOrder, true);

        $this->assertNull($transaction);
    }

    public function test_sync_sales_order_syncs_partial_payment(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $partialOrder = $this->createCoreSalesOrder([
            'total_amount' => 1000.00,
            'paid_amount' => 500.00,
            'payment_status' => 'partial',
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncSalesOrder($partialOrder, true);

        $this->assertNotNull($transaction);
        $this->assertEquals('income', $transaction->type);
    }

    public function test_sync_sales_order_creates_sales_category_if_missing(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        // Ensure no Sales category exists
        BasicTransactionCategory::where('name', 'Sales')->delete();

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertNotNull($transaction);
        $this->assertNotNull($transaction->category);
        $this->assertEquals('Sales', $transaction->category->name);
        $this->assertEquals('income', $transaction->category->type);
    }

    public function test_sync_sales_order_updates_existing_transaction(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder([
            'total_amount' => 1000.00,
            'created_by_id' => $admin->id,
        ]);

        // First sync
        $transaction1 = $this->syncService->syncSalesOrder($salesOrder, true);

        // Update order amount
        $salesOrder->update(['total_amount' => 1500.00]);

        // Second sync should update
        $transaction2 = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertEquals($transaction1->id, $transaction2->id);
        $this->assertEquals(1500.00, $transaction2->amount);
    }

    public function test_sync_sales_order_respects_auto_sync_setting(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        // Disable auto-sync
        app(\App\Services\Settings\ModuleSettingsService::class)
            ->set('AccountingCore', 'auto_sync_sales_orders', false);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        // Without force flag
        $transaction = $this->syncService->syncSalesOrder($salesOrder, false);

        $this->assertNull($transaction);
    }

    public function test_sync_with_force_flag_overrides_setting(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        // Disable auto-sync
        app(\App\Services\Settings\ModuleSettingsService::class)
            ->set('AccountingCore', 'auto_sync_sales_orders', false);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        // With force flag
        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertNotNull($transaction);
        $this->assertEquals('manual', $transaction->sync_status);
    }

    // ==========================================
    // Purchase Order Sync Tests
    // ==========================================

    public function test_sync_purchase_order_creates_expense_transaction(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $purchaseOrder = $this->createPaidPurchaseOrder([
            'total_amount' => 2000.00,
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncPurchaseOrder($purchaseOrder, true);

        $this->assertNotNull($transaction);
        $this->assertInstanceOf(BasicTransaction::class, $transaction);
        $this->assertEquals('expense', $transaction->type);
        $this->assertEquals(2000.00, $transaction->amount);
        $this->assertEquals(CorePurchaseOrder::class, $transaction->sourceable_type);
        $this->assertEquals($purchaseOrder->id, $transaction->sourceable_id);
    }

    public function test_sync_purchase_order_only_syncs_when_paid(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $pendingOrder = $this->createCorePurchaseOrder([
            'payment_status' => 'pending',
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncPurchaseOrder($pendingOrder, true);

        $this->assertNull($transaction);
    }

    public function test_sync_purchase_order_creates_purchases_category_if_missing(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        // Ensure no Purchases category exists
        BasicTransactionCategory::where('name', 'Purchases')->delete();

        $purchaseOrder = $this->createPaidPurchaseOrder(['created_by_id' => $admin->id]);

        $transaction = $this->syncService->syncPurchaseOrder($purchaseOrder, true);

        $this->assertNotNull($transaction);
        $this->assertNotNull($transaction->category);
        $this->assertEquals('Purchases', $transaction->category->name);
        $this->assertEquals('expense', $transaction->category->type);
    }

    // ==========================================
    // Unsync Tests
    // ==========================================

    public function test_unsync_order_removes_transaction(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        // Sync first
        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);
        $this->assertNotNull($transaction);

        // Unsync
        $result = $this->syncService->unsyncOrder($salesOrder);

        $this->assertTrue($result);
        $this->assertDatabaseMissing('basic_transactions', [
            'sourceable_type' => CoreSalesOrder::class,
            'sourceable_id' => $salesOrder->id,
        ]);
    }

    public function test_is_order_synced_returns_correct_status(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        // Before sync
        $this->assertFalse($this->syncService->isOrderSynced($salesOrder));

        // After sync
        $this->syncService->syncSalesOrder($salesOrder, true);
        $this->assertTrue($this->syncService->isOrderSynced($salesOrder));
    }

    // ==========================================
    // Posting Log Tests
    // ==========================================

    public function test_successful_sync_creates_posting_log(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder([
            'total_amount' => 999.99,
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        // Verify posting log was created
        $this->assertDatabaseHas('posting_logs', [
            'source_module' => 'SystemCore',
            'source_entity_type' => 'sales_order',
            'source_entity_id' => $salesOrder->id,
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'transaction',
            'target_entity_id' => $transaction->id,
            'action' => 'create',
            'status' => 'success',
        ]);
    }

    public function test_posting_log_records_update_action(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        // First sync
        $this->syncService->syncSalesOrder($salesOrder, true);

        // Clear logs
        PostingLog::query()->delete();

        // Update and sync again
        $salesOrder->update(['total_amount' => 2000.00]);
        $this->syncService->syncSalesOrder($salesOrder, true);

        // Verify update log
        $this->assertDatabaseHas('posting_logs', [
            'source_entity_id' => $salesOrder->id,
            'action' => 'update',
            'status' => 'success',
        ]);
    }

    public function test_posting_log_records_delete_action(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);
        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);
        $transactionId = $transaction->id;

        // Clear logs
        PostingLog::query()->delete();

        // Unsync
        $this->syncService->unsyncOrder($salesOrder);

        // Verify delete log
        $this->assertDatabaseHas('posting_logs', [
            'source_entity_id' => $salesOrder->id,
            'target_entity_id' => $transactionId,
            'action' => 'delete',
            'status' => 'success',
        ]);
    }

    // ==========================================
    // Event Integration Tests
    // ==========================================

    public function test_sales_order_dispatches_event_on_payment_status_change(): void
    {
        Event::fake([SalesOrderPaymentStatusChanged::class]);

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createCoreSalesOrder([
            'payment_status' => 'pending',
            'created_by_id' => $admin->id,
        ]);

        $salesOrder->update(['payment_status' => 'paid']);

        Event::assertDispatched(SalesOrderPaymentStatusChanged::class, function ($event) use ($salesOrder) {
            return $event->salesOrder->id === $salesOrder->id
                && $event->oldStatus === 'pending'
                && $event->newStatus === 'paid';
        });
    }

    public function test_purchase_order_dispatches_event_on_payment_status_change(): void
    {
        Event::fake([PurchaseOrderPaymentStatusChanged::class]);

        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $purchaseOrder = $this->createCorePurchaseOrder([
            'payment_status' => 'pending',
            'created_by_id' => $admin->id,
        ]);

        $purchaseOrder->update(['payment_status' => 'paid']);

        Event::assertDispatched(PurchaseOrderPaymentStatusChanged::class, function ($event) use ($purchaseOrder) {
            return $event->purchaseOrder->id === $purchaseOrder->id
                && $event->oldStatus === 'pending'
                && $event->newStatus === 'paid';
        });
    }

    // ==========================================
    // Bulk Sync Tests
    // ==========================================

    public function test_bulk_sync_processes_multiple_eligible_orders(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        // Create multiple orders with different statuses
        $this->createPaidSalesOrder(['total_amount' => 100, 'created_by_id' => $admin->id]);
        $this->createPaidSalesOrder(['total_amount' => 200, 'created_by_id' => $admin->id]);
        $this->createCoreSalesOrder([
            'total_amount' => 300,
            'paid_amount' => 150,
            'payment_status' => 'partial',
            'created_by_id' => $admin->id,
        ]);
        $this->createCoreSalesOrder([
            'payment_status' => 'pending',
            'created_by_id' => $admin->id,
        ]);

        $result = $this->syncService->bulkSyncOrders('sales');

        // Should sync paid and partial orders (3 total)
        $this->assertCount(3, $result['synced']);
        $this->assertEmpty($result['errors']);
        $this->assertEquals(3, $result['total']);
    }

    public function test_bulk_sync_handles_purchase_orders(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $this->createPaidPurchaseOrder(['created_by_id' => $admin->id]);
        $this->createPaidPurchaseOrder(['created_by_id' => $admin->id]);

        $result = $this->syncService->bulkSyncOrders('purchases');

        $this->assertCount(2, $result['synced']);
        $this->assertEmpty($result['errors']);
    }

    // ==========================================
    // Get Transaction Tests
    // ==========================================

    public function test_get_transaction_for_order_returns_linked_transaction(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);
        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $result = $this->syncService->getTransactionForOrder($salesOrder);

        $this->assertNotNull($result);
        $this->assertEquals($transaction->id, $result->id);
    }

    public function test_get_transaction_for_order_returns_null_when_not_synced(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        $result = $this->syncService->getTransactionForOrder($salesOrder);

        $this->assertNull($result);
    }

    // ==========================================
    // Transaction Content Tests
    // ==========================================

    public function test_transaction_contains_order_reference(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertEquals($salesOrder->order_number, $transaction->reference_number);
        $this->assertStringContainsString($salesOrder->order_number, $transaction->description);
    }

    public function test_transaction_contains_customer_info(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $customer = $this->createCoreCustomer([
            'name' => 'Test Customer Inc',
            'created_by_id' => $admin->id,
        ]);

        $salesOrder = $this->createPaidSalesOrder([
            'customer_id' => $customer->id,
            'created_by_id' => $admin->id,
        ]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertStringContainsString('Test Customer Inc', $transaction->description);
    }

    public function test_transaction_has_correct_tags(): void
    {
        $admin = $this->createAdmin();
        $this->actingAs($admin);

        $salesOrder = $this->createPaidSalesOrder(['created_by_id' => $admin->id]);

        $transaction = $this->syncService->syncSalesOrder($salesOrder, true);

        $this->assertIsArray($transaction->tags);
        $this->assertContains('sales', $transaction->tags);
        $this->assertContains('automated', $transaction->tags);
    }
}
