<?php

namespace Tests\Unit\SystemCore\Models;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\SystemCore\App\Models\PostingLog;
use Tests\TestCase;
use Tests\Traits\CreatesSystemCoreEntities;
use Tests\Traits\CreatesUsers;

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

    // ==========================================
    // Creation Tests
    // ==========================================

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

        $log = PostingLog::create([
            'source_module' => 'SystemCore',
            'source_entity_type' => 'sales_order',
            'source_entity_id' => 1,
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'transaction',
            'target_entity_id' => 1,
            'action' => 'create',
            'status' => 'success',
            'posted_by' => $admin->id,
            'posted_at' => now(),
        ]);

        $this->assertDatabaseHas('posting_logs', [
            'source_module' => 'SystemCore',
            'target_module' => 'AccountingCore',
            'action' => 'create',
            'status' => 'success',
        ]);
        $this->assertNotNull($log->id);
    }

    // ==========================================
    // Scope Tests
    // ==========================================

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

        $todayLog = $this->createPostingLog([
            'posted_at' => now(),
            'posted_by' => $admin->id,
        ]);

        $yesterdayLog = $this->createPostingLog([
            'posted_at' => now()->subDay(),
            'posted_by' => $admin->id,
        ]);

        $todayLogs = PostingLog::today()->get();

        $this->assertTrue($todayLogs->contains($todayLog));
        $this->assertFalse($todayLogs->contains($yesterdayLog));
    }

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

        $withinRange = $this->createPostingLog([
            'posted_at' => now()->subDays(3),
            'posted_by' => $admin->id,
        ]);

        $outsideRange = $this->createPostingLog([
            'posted_at' => now()->subDays(10),
            'posted_by' => $admin->id,
        ]);

        $rangeStart = now()->subDays(5);
        $rangeEnd = now();

        $rangeLogs = PostingLog::forDateRange($rangeStart, $rangeEnd)->get();

        $this->assertTrue($rangeLogs->contains($withinRange));
        $this->assertFalse($rangeLogs->contains($outsideRange));
    }

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

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $failedLog = $this->createFailedPostingLog([
            'posted_by' => $admin->id,
        ]);

        $successfulLogs = PostingLog::successful()->get();

        $this->assertTrue($successfulLogs->contains($successLog));
        $this->assertFalse($successfulLogs->contains($failedLog));
    }

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

        $failedLog = $this->createFailedPostingLog([
            'posted_by' => $admin->id,
        ]);

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $failedLogs = PostingLog::failed()->get();

        $this->assertTrue($failedLogs->contains($failedLog));
        $this->assertFalse($failedLogs->contains($successLog));
    }

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

        $pendingLog = $this->createPendingPostingLog([
            'posted_by' => $admin->id,
        ]);

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $pendingLogs = PostingLog::pending()->get();

        $this->assertTrue($pendingLogs->contains($pendingLog));
        $this->assertFalse($pendingLogs->contains($successLog));
    }

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

        $systemCoreSource = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'target_module' => 'AccountingCore',
            'posted_by' => $admin->id,
        ]);

        $accountingTarget = $this->createPostingLog([
            'source_module' => 'WMSCore',
            'target_module' => 'SystemCore',
            'posted_by' => $admin->id,
        ]);

        $unrelatedLog = $this->createPostingLog([
            'source_module' => 'CRMCore',
            'target_module' => 'PMCore',
            'posted_by' => $admin->id,
        ]);

        $systemCoreLogs = PostingLog::forModule('SystemCore')->get();

        $this->assertTrue($systemCoreLogs->contains($systemCoreSource));
        $this->assertTrue($systemCoreLogs->contains($accountingTarget));
        $this->assertFalse($systemCoreLogs->contains($unrelatedLog));
    }

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

        $fromSystemCore = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'target_module' => 'AccountingCore',
            'posted_by' => $admin->id,
        ]);

        $toSystemCore = $this->createPostingLog([
            'source_module' => 'CRMCore',
            'target_module' => 'SystemCore',
            'posted_by' => $admin->id,
        ]);

        $fromLogs = PostingLog::fromModule('SystemCore')->get();

        $this->assertTrue($fromLogs->contains($fromSystemCore));
        $this->assertFalse($fromLogs->contains($toSystemCore));
    }

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

        $toAccounting = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'target_module' => 'AccountingCore',
            'posted_by' => $admin->id,
        ]);

        $fromAccounting = $this->createPostingLog([
            'source_module' => 'AccountingCore',
            'target_module' => 'ReportCore',
            'posted_by' => $admin->id,
        ]);

        $toLogs = PostingLog::toModule('AccountingCore')->get();

        $this->assertTrue($toLogs->contains($toAccounting));
        $this->assertFalse($toLogs->contains($fromAccounting));
    }

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

        $salesOrderSource = $this->createPostingLog([
            'source_entity_type' => 'sales_order',
            'target_entity_type' => 'transaction',
            'posted_by' => $admin->id,
        ]);

        $salesOrderTarget = $this->createPostingLog([
            'source_entity_type' => 'payment',
            'target_entity_type' => 'sales_order',
            'posted_by' => $admin->id,
        ]);

        $unrelatedLog = $this->createPostingLog([
            'source_entity_type' => 'purchase_order',
            'target_entity_type' => 'transaction',
            'posted_by' => $admin->id,
        ]);

        $salesOrderLogs = PostingLog::forEntityType('sales_order')->get();

        $this->assertTrue($salesOrderLogs->contains($salesOrderSource));
        $this->assertTrue($salesOrderLogs->contains($salesOrderTarget));
        $this->assertFalse($salesOrderLogs->contains($unrelatedLog));
    }

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

        $salesOrderLog = $this->createPostingLog([
            'source_entity_type' => 'sales_order',
            'posted_by' => $admin->id,
        ]);

        $purchaseOrderLog = $this->createPostingLog([
            'source_entity_type' => 'purchase_order',
            'posted_by' => $admin->id,
        ]);

        $sourceLogs = PostingLog::forSourceEntityType('sales_order')->get();

        $this->assertTrue($sourceLogs->contains($salesOrderLog));
        $this->assertFalse($sourceLogs->contains($purchaseOrderLog));
    }

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

        $transactionLog = $this->createPostingLog([
            'target_entity_type' => 'transaction',
            'posted_by' => $admin->id,
        ]);

        $journalLog = $this->createPostingLog([
            'target_entity_type' => 'journal',
            'posted_by' => $admin->id,
        ]);

        $targetLogs = PostingLog::forTargetEntityType('transaction')->get();

        $this->assertTrue($targetLogs->contains($transactionLog));
        $this->assertFalse($targetLogs->contains($journalLog));
    }

    // ==========================================
    // Retry Method Tests
    // ==========================================

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

        $failedLog = $this->createFailedPostingLog([
            'retry_count' => 0,
            'posted_by' => $admin->id,
        ]);

        $this->assertTrue($failedLog->canRetry());
    }

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

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $this->assertFalse($successLog->canRetry());
    }

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

        $log = $this->createFailedPostingLog([
            'retry_count' => 0,
            'posted_by' => $admin->id,
        ]);

        $log->incrementRetry();
        $log->refresh();

        $this->assertEquals(1, $log->retry_count);
    }

    // ==========================================
    // Status Check Method Tests
    // ==========================================

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

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $failedLog = $this->createFailedPostingLog([
            'posted_by' => $admin->id,
        ]);

        $this->assertTrue($successLog->isSuccessful());
        $this->assertFalse($failedLog->isSuccessful());
    }

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

        $failedLog = $this->createFailedPostingLog([
            'posted_by' => $admin->id,
        ]);

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $this->assertTrue($failedLog->isFailed());
        $this->assertFalse($successLog->isFailed());
    }

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

        $pendingLog = $this->createPendingPostingLog([
            'posted_by' => $admin->id,
        ]);

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $this->assertTrue($pendingLog->isPending());
        $this->assertFalse($successLog->isPending());
    }

    // ==========================================
    // Accessor Tests
    // ==========================================

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

        $log = $this->createPostingLog([
            'amount' => 1500.50,
            'posted_by' => $admin->id,
        ]);

        $formattedAmount = $log->formatted_amount;
        $this->assertNotNull($formattedAmount);
    }

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

        $log = $this->createPostingLog([
            'posted_at' => now()->setDate(2025, 1, 15)->setTime(10, 30),
            'posted_by' => $admin->id,
        ]);

        $this->assertStringContainsString('Jan', $log->formatted_posted_at);
        $this->assertStringContainsString('15', $log->formatted_posted_at);
        $this->assertStringContainsString('2025', $log->formatted_posted_at);
    }

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

        $successLog = $this->createPostingLog([
            'status' => 'success',
            'posted_by' => $admin->id,
        ]);

        $badge = $successLog->status_badge;

        $this->assertStringContainsString('<span', $badge);
        $this->assertStringContainsString('badge', $badge);
        $this->assertStringContainsString('success', strtolower($badge));
    }

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

        $log = $this->createPostingLog([
            'action' => 'create',
            'posted_by' => $admin->id,
        ]);

        $badge = $log->action_badge;

        $this->assertStringContainsString('<span', $badge);
        $this->assertStringContainsString('badge', $badge);
    }

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

        $log = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'source_entity_type' => 'sales_order',
            'source_entity_id' => 123,
            'posted_by' => $admin->id,
        ]);

        $sourceRef = $log->source_reference;

        $this->assertStringContainsString('SystemCore', $sourceRef);
        $this->assertStringContainsString('sales_order', $sourceRef);
        $this->assertStringContainsString('123', $sourceRef);
    }

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

        $log = $this->createPostingLog([
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'transaction',
            'target_entity_id' => 456,
            'posted_by' => $admin->id,
        ]);

        $targetRef = $log->target_reference;

        $this->assertStringContainsString('AccountingCore', $targetRef);
        $this->assertStringContainsString('transaction', $targetRef);
        $this->assertStringContainsString('456', $targetRef);
    }

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

        $log = $this->createPendingPostingLog([
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'transaction',
            'target_entity_id' => null,
            'posted_by' => $admin->id,
        ]);

        $targetRef = $log->target_reference;

        $this->assertStringContainsString('AccountingCore', $targetRef);
        $this->assertStringContainsString('transaction', $targetRef);
        $this->assertStringNotContainsString('#', $targetRef);
    }

    // ==========================================
    // Static Method Tests
    // ==========================================

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

        // Create various logs
        $this->createPostingLog(['status' => 'success', 'posted_by' => $admin->id]);
        $this->createPostingLog(['status' => 'success', 'posted_by' => $admin->id]);
        $this->createFailedPostingLog(['posted_by' => $admin->id]);
        $this->createPendingPostingLog(['posted_by' => $admin->id]);

        $stats = PostingLog::getStatistics();

        $this->assertEquals(4, $stats['total']);
        $this->assertEquals(2, $stats['successful']);
        $this->assertEquals(1, $stats['failed']);
        $this->assertEquals(1, $stats['pending']);
        $this->assertArrayHasKey('today', $stats);
    }

    // ==========================================
    // Relationship Tests
    // ==========================================

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

        $log = $this->createPostingLog([
            'posted_by' => $admin->id,
        ]);

        $this->assertNotNull($log->postedBy);
        $this->assertInstanceOf(User::class, $log->postedBy);
        $this->assertEquals($admin->id, $log->postedBy->id);
    }

    // ==========================================
    // Casting Tests
    // ==========================================

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

        $oldValues = ['field1' => 'old_value1', 'field2' => 'old_value2'];

        $log = $this->createPostingLog([
            'old_values' => $oldValues,
            'posted_by' => $admin->id,
        ]);

        $this->assertEquals($oldValues, $log->old_values);
        $this->assertIsArray($log->old_values);
    }

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

        $newValues = ['field1' => 'new_value1', 'field2' => 'new_value2'];

        $log = $this->createPostingLog([
            'new_values' => $newValues,
            'posted_by' => $admin->id,
        ]);

        $this->assertEquals($newValues, $log->new_values);
        $this->assertIsArray($log->new_values);
    }

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

        $log = $this->createPostingLog([
            'amount' => 1234.56,
            'posted_by' => $admin->id,
        ]);

        $this->assertEquals(1234.56, $log->amount);
    }

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

        $log = $this->createFailedPostingLog([
            'retry_count' => 2,
            'posted_by' => $admin->id,
        ]);

        $this->assertEquals(2, $log->retry_count);
        $this->assertIsInt($log->retry_count);
    }
}
