<?php

namespace Tests\Unit\SystemCore\Services;

use App\Services\Settings\ModuleSettingsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\SystemCore\App\Models\PostingLog;
use Modules\SystemCore\App\Services\PostingLogService;
use Tests\TestCase;
use Tests\Traits\CreatesSystemCoreEntities;
use Tests\Traits\CreatesUsers;

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

    protected PostingLogService $service;

    protected ModuleSettingsService $settingsService;

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

        $this->settingsService = app(ModuleSettingsService::class);
        $this->service = new PostingLogService($this->settingsService);
    }

    // ==========================================
    // Settings Methods Tests
    // ==========================================

    public function test_is_enabled_returns_true_by_default(): void
    {
        $this->assertTrue($this->service->isEnabled());
    }

    public function test_is_enabled_returns_setting_value(): void
    {
        $this->settingsService->set('SystemCore', 'enable_posting_log', false);

        $this->assertFalse($this->service->isEnabled());
    }

    public function test_get_max_retry_attempts_returns_default(): void
    {
        $attempts = $this->service->getMaxRetryAttempts();

        $this->assertIsInt($attempts);
        $this->assertEquals(3, $attempts);
    }

    public function test_get_max_retry_attempts_returns_setting_value(): void
    {
        $this->settingsService->set('SystemCore', 'max_retry_attempts', 5);

        $this->assertEquals(5, $this->service->getMaxRetryAttempts());
    }

    public function test_get_retention_days_returns_default(): void
    {
        $days = $this->service->getRetentionDays();

        $this->assertIsInt($days);
        $this->assertEquals(90, $days);
    }

    public function test_get_retention_days_returns_setting_value(): void
    {
        $this->settingsService->set('SystemCore', 'posting_log_retention_days', 30);

        $this->assertEquals(30, $this->service->getRetentionDays());
    }

    // ==========================================
    // Log Success Tests
    // ==========================================

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

        $log = $this->service->logSuccess(
            sourceModule: 'SystemCore',
            sourceType: 'SalesOrder',
            sourceId: 1,
            targetModule: 'AccountingCore',
            targetType: 'Transaction',
            targetId: 100,
            action: 'create',
            description: 'Created transaction from sales order',
            amount: 1500.50,
            oldValues: ['status' => 'pending'],
            newValues: ['status' => 'posted']
        );

        $this->assertNotNull($log);
        $this->assertInstanceOf(PostingLog::class, $log);
        $this->assertEquals('SystemCore', $log->source_module);
        $this->assertEquals('SalesOrder', $log->source_entity_type);
        $this->assertEquals(1, $log->source_entity_id);
        $this->assertEquals('AccountingCore', $log->target_module);
        $this->assertEquals('Transaction', $log->target_entity_type);
        $this->assertEquals(100, $log->target_entity_id);
        $this->assertEquals('create', $log->action);
        $this->assertEquals('success', $log->status);
        $this->assertEquals('Created transaction from sales order', $log->description);
        $this->assertEquals(1500.50, $log->amount);
        $this->assertEquals(['status' => 'pending'], $log->old_values);
        $this->assertEquals(['status' => 'posted'], $log->new_values);
        $this->assertEquals($admin->id, $log->posted_by);
    }

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

        $this->settingsService->set('SystemCore', 'enable_posting_log', false);

        $log = $this->service->logSuccess(
            sourceModule: 'SystemCore',
            sourceType: 'SalesOrder',
            sourceId: 1,
            targetModule: 'AccountingCore',
            targetType: 'Transaction',
            targetId: 100,
            action: 'create'
        );

        $this->assertNull($log);
        $this->assertDatabaseCount('posting_logs', 0);
    }

    // ==========================================
    // Log Failure Tests
    // ==========================================

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

        $log = $this->service->logFailure(
            sourceModule: 'SystemCore',
            sourceType: 'SalesOrder',
            sourceId: 1,
            targetModule: 'AccountingCore',
            targetType: 'Transaction',
            action: 'create',
            errorMessage: 'Insufficient account balance',
            description: 'Failed to create transaction',
            amount: 5000.00
        );

        $this->assertNotNull($log);
        $this->assertEquals('failed', $log->status);
        $this->assertEquals('Insufficient account balance', $log->error_message);
        $this->assertNull($log->target_entity_id);
    }

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

        $this->settingsService->set('SystemCore', 'enable_posting_log', false);

        $log = $this->service->logFailure(
            sourceModule: 'SystemCore',
            sourceType: 'SalesOrder',
            sourceId: 1,
            targetModule: 'AccountingCore',
            targetType: 'Transaction',
            action: 'create',
            errorMessage: 'Error'
        );

        $this->assertNull($log);
    }

    // ==========================================
    // Log Pending Tests
    // ==========================================

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

        $log = $this->service->logPending(
            sourceModule: 'SystemCore',
            sourceType: 'PurchaseOrder',
            sourceId: 5,
            targetModule: 'WMSInventoryCore',
            targetType: 'InventoryTransaction',
            action: 'receive',
            description: 'Pending inventory receive',
            amount: 2500.00,
            newValues: ['quantity' => 100]
        );

        $this->assertNotNull($log);
        $this->assertEquals('pending', $log->status);
        $this->assertNull($log->target_entity_id);
        $this->assertEquals(['quantity' => 100], $log->new_values);
    }

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

        $this->settingsService->set('SystemCore', 'enable_posting_log', false);

        $log = $this->service->logPending(
            sourceModule: 'SystemCore',
            sourceType: 'PurchaseOrder',
            sourceId: 5,
            targetModule: 'WMSInventoryCore',
            targetType: 'InventoryTransaction',
            action: 'receive'
        );

        $this->assertNull($log);
    }

    // ==========================================
    // Mark Status Tests
    // ==========================================

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

        $log = $this->createPendingPostingLog();
        $this->assertEquals(0, $log->retry_count);

        $updatedLog = $this->service->markRetried($log);

        $this->assertEquals(1, $updatedLog->retry_count);

        $updatedLog = $this->service->markRetried($updatedLog);
        $this->assertEquals(2, $updatedLog->retry_count);
    }

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

        $log = $this->createPendingPostingLog();
        $this->assertEquals('pending', $log->status);
        $this->assertNull($log->target_entity_id);

        $updatedLog = $this->service->markSuccessful($log, 999, ['result' => 'completed']);

        $this->assertEquals('success', $updatedLog->status);
        $this->assertEquals(999, $updatedLog->target_entity_id);
        $this->assertEquals(['result' => 'completed'], $updatedLog->new_values);
    }

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

        $log = $this->createFailedPostingLog();
        $this->assertNotNull($log->error_message);

        $updatedLog = $this->service->markSuccessful($log, 100);

        $this->assertEquals('success', $updatedLog->status);
        $this->assertNull($updatedLog->error_message);
    }

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

        $log = $this->createPendingPostingLog();

        $updatedLog = $this->service->markFailed($log, 'Connection timeout');

        $this->assertEquals('failed', $updatedLog->status);
        $this->assertEquals('Connection timeout', $updatedLog->error_message);
    }

    // ==========================================
    // Statistics Tests
    // ==========================================

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

        // Create some logs
        $this->createPostingLog(['status' => 'success']);
        $this->createPostingLog(['status' => 'success']);
        $this->createPostingLog(['status' => 'failed']);
        $this->createPendingPostingLog();

        $stats = $this->service->getStatistics();

        $this->assertArrayHasKey('total', $stats);
        $this->assertArrayHasKey('by_status', $stats);
        $this->assertArrayHasKey('by_source_module', $stats);
        $this->assertArrayHasKey('by_target_module', $stats);
        $this->assertArrayHasKey('by_action', $stats);
        $this->assertArrayHasKey('recent_failures', $stats);
        $this->assertArrayHasKey('success_rate', $stats);
    }

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

        // Create 3 success, 1 failed = 75% success rate
        $this->createPostingLog(['status' => 'success']);
        $this->createPostingLog(['status' => 'success']);
        $this->createPostingLog(['status' => 'success']);
        $this->createPostingLog(['status' => 'failed']);

        $stats = $this->service->getStatistics();

        $this->assertEquals(4, $stats['total']);
        $this->assertEquals(75.0, $stats['success_rate']);
    }

    public function test_get_statistics_returns_zero_rate_when_empty(): void
    {
        $stats = $this->service->getStatistics();

        $this->assertEquals(0, $stats['total']);
        $this->assertEquals(0.0, $stats['success_rate']);
    }

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

        // Recent failure (within 7 days)
        $this->createPostingLog(['status' => 'failed', 'posted_at' => now()->subDays(3)]);
        // Old failure (more than 7 days)
        $this->createPostingLog(['status' => 'failed', 'posted_at' => now()->subDays(10)]);

        $stats = $this->service->getStatistics();

        $this->assertEquals(1, $stats['recent_failures']);
    }

    // ==========================================
    // Get Logs Tests
    // ==========================================

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

        // Create matching logs
        $log1 = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'source_entity_type' => 'SalesOrder',
            'source_entity_id' => 1,
        ]);
        $log2 = $this->createPostingLog([
            'source_module' => 'SystemCore',
            'source_entity_type' => 'SalesOrder',
            'source_entity_id' => 1,
        ]);
        // Non-matching log
        $this->createPostingLog([
            'source_module' => 'SystemCore',
            'source_entity_type' => 'SalesOrder',
            'source_entity_id' => 2,
        ]);

        $logs = $this->service->getLogsForSource('SystemCore', 'SalesOrder', 1);

        $this->assertCount(2, $logs);
        $this->assertTrue($logs->contains($log1));
        $this->assertTrue($logs->contains($log2));
    }

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

        $log1 = $this->createPostingLog([
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'Transaction',
            'target_entity_id' => 500,
        ]);
        // Non-matching
        $this->createPostingLog([
            'target_module' => 'AccountingCore',
            'target_entity_type' => 'Transaction',
            'target_entity_id' => 501,
        ]);

        $logs = $this->service->getLogsForTarget('AccountingCore', 'Transaction', 500);

        $this->assertCount(1, $logs);
        $this->assertTrue($logs->contains($log1));
    }

    // ==========================================
    // Retry Queue Tests
    // ==========================================

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

        // Under max retries (should be included)
        $pendingLow = $this->createPendingPostingLog(['retry_count' => 1]);
        // At max retries (should be excluded with max=3)
        $this->createPendingPostingLog(['retry_count' => 3]);
        // Over max retries (should be excluded)
        $this->createPendingPostingLog(['retry_count' => 5]);

        $logs = $this->service->getPendingForRetry(3);

        $this->assertCount(1, $logs);
        $this->assertTrue($logs->contains($pendingLow));
    }

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

        $this->settingsService->set('SystemCore', 'max_retry_attempts', 2);

        $this->createPendingPostingLog(['retry_count' => 0]);
        $this->createPendingPostingLog(['retry_count' => 1]);
        $this->createPendingPostingLog(['retry_count' => 2]); // At limit - excluded

        $logs = $this->service->getPendingForRetry();

        $this->assertCount(2, $logs);
    }

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

        // Under max
        $failedLow = $this->createFailedPostingLog(['retry_count' => 0]);
        // At max
        $this->createFailedPostingLog(['retry_count' => 3]);

        $logs = $this->service->getFailedForRetry(3);

        $this->assertCount(1, $logs);
        $this->assertTrue($logs->contains($failedLow));
    }

    // ==========================================
    // Purge Tests
    // ==========================================

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

        // Old log (should be deleted)
        $this->createPostingLog(['posted_at' => now()->subDays(100)]);
        // Recent log (should be kept)
        $recentLog = $this->createPostingLog(['posted_at' => now()->subDays(30)]);

        $deleted = $this->service->purgeOldLogs(90);

        $this->assertEquals(1, $deleted);
        $this->assertDatabaseHas('posting_logs', ['id' => $recentLog->id]);
    }

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

        $this->createPostingLog(['posted_at' => now()->subDays(1000)]);

        // 0 means keep forever
        $deleted = $this->service->purgeOldLogs(0);

        $this->assertEquals(0, $deleted);
        $this->assertDatabaseCount('posting_logs', 1);
    }

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

        $this->settingsService->set('SystemCore', 'posting_log_retention_days', 30);

        // Old (should be deleted)
        $this->createPostingLog(['posted_at' => now()->subDays(40)]);
        // Recent (should be kept)
        $this->createPostingLog(['posted_at' => now()->subDays(20)]);

        $deleted = $this->service->purgeOldLogs();

        $this->assertEquals(1, $deleted);
        $this->assertDatabaseCount('posting_logs', 1);
    }
}
