<?php

namespace Tests\Unit\AccountingCore\Models;

use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\AccountingCore\App\Models\BasicTransaction;
use Modules\AccountingCore\App\Models\BasicTransactionCategory;
use Tests\TestCase;
use Tests\Traits\CreatesAccountingCoreEntities;
use Tests\Traits\CreatesUsers;

class BasicTransactionTest extends TestCase
{
    use CreatesAccountingCoreEntities;
    use CreatesUsers;
    use RefreshDatabase;

    // ==========================================
    // Creation and Auto-Generation Tests
    // ==========================================

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

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

        $transaction = BasicTransaction::create([
            'type' => 'income',
            'amount' => 1500.50,
            'category_id' => $category->id,
            'transaction_date' => now(),
            'created_by_id' => $admin->id,
        ]);

        $this->assertNotNull($transaction->id);
        $this->assertEquals('income', $transaction->type);
        $this->assertEquals(1500.50, $transaction->amount);
        $this->assertEquals($category->id, $transaction->category_id);
    }

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

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

        $this->assertNotNull($transaction->transaction_number);
        $this->assertStringStartsWith('TXN', $transaction->transaction_number);
    }

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

        $transaction = $this->createIncomeTransaction([
            'transaction_number' => 'CUSTOM-123',
            'created_by_id' => $admin->id,
        ]);

        $this->assertEquals('CUSTOM-123', $transaction->transaction_number);
    }

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

        $transaction1 = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $transaction2 = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $transaction3 = $this->createIncomeTransaction(['created_by_id' => $admin->id]);

        $this->assertNotEquals($transaction1->transaction_number, $transaction2->transaction_number);
        $this->assertNotEquals($transaction2->transaction_number, $transaction3->transaction_number);
    }

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

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

        $transaction = $this->createIncomeTransaction([
            'amount' => 1234.5678,
            'created_by_id' => $admin->id,
        ]);

        $this->assertIsNumeric($transaction->amount);
        // Decimal:2 cast rounds to 2 decimal places
        $this->assertEquals(1234.57, $transaction->amount);
    }

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

        $transaction = $this->createIncomeTransaction([
            'transaction_date' => '2026-01-15',
            'created_by_id' => $admin->id,
        ]);

        $this->assertInstanceOf(Carbon::class, $transaction->transaction_date);
        $this->assertEquals('2026-01-15', $transaction->transaction_date->toDateString());
    }

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

        $tags = ['sales', 'urgent', 'q1'];
        $transaction = $this->createIncomeTransaction([
            'tags' => $tags,
            'created_by_id' => $admin->id,
        ]);

        $this->assertIsArray($transaction->tags);
        $this->assertEquals($tags, $transaction->tags);
    }

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

        $customFields = ['project_id' => 123, 'department' => 'Sales'];
        $transaction = $this->createIncomeTransaction([
            'custom_fields' => $customFields,
            'created_by_id' => $admin->id,
        ]);

        $this->assertIsArray($transaction->custom_fields);
        $this->assertEquals($customFields, $transaction->custom_fields);
    }

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

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

        $incomeTransaction = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $expenseTransaction = $this->createExpenseTransaction(['created_by_id' => $admin->id]);

        $incomeTransactions = BasicTransaction::income()->get();

        $this->assertTrue($incomeTransactions->contains($incomeTransaction));
        $this->assertFalse($incomeTransactions->contains($expenseTransaction));
    }

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

        $incomeTransaction = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $expenseTransaction = $this->createExpenseTransaction(['created_by_id' => $admin->id]);

        $expenseTransactions = BasicTransaction::expense()->get();

        $this->assertTrue($expenseTransactions->contains($expenseTransaction));
        $this->assertFalse($expenseTransactions->contains($incomeTransaction));
    }

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

        $inRange = $this->createTransactionForDate(now()->subDays(5), ['created_by_id' => $admin->id]);
        $beforeRange = $this->createTransactionForDate(now()->subDays(30), ['created_by_id' => $admin->id]);
        $afterRange = $this->createTransactionForDate(now()->addDays(10), ['created_by_id' => $admin->id]);

        $transactions = BasicTransaction::forDateRange(now()->subDays(10), now())->get();

        $this->assertTrue($transactions->contains($inRange));
        $this->assertFalse($transactions->contains($beforeRange));
        $this->assertFalse($transactions->contains($afterRange));
    }

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

        $thisMonth = $this->createTransactionForDate(now(), ['created_by_id' => $admin->id]);
        $lastMonth = $this->createTransactionForDate(now()->subMonth(), ['created_by_id' => $admin->id]);

        $transactions = BasicTransaction::currentMonth()->get();

        $this->assertTrue($transactions->contains($thisMonth));
        $this->assertFalse($transactions->contains($lastMonth));
    }

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

        $thisYear = $this->createTransactionForDate(now(), ['created_by_id' => $admin->id]);
        $lastYear = $this->createTransactionForDate(now()->subYear(), ['created_by_id' => $admin->id]);

        $transactions = BasicTransaction::currentYear()->get();

        $this->assertTrue($transactions->contains($thisYear));
        $this->assertFalse($transactions->contains($lastYear));
    }

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

        $category1 = $this->createIncomeCategory(['created_by_id' => $admin->id]);
        $category2 = $this->createIncomeCategory(['created_by_id' => $admin->id]);

        $transaction1 = $this->createIncomeTransaction([
            'category_id' => $category1->id,
            'created_by_id' => $admin->id,
        ]);
        $transaction2 = $this->createIncomeTransaction([
            'category_id' => $category2->id,
            'created_by_id' => $admin->id,
        ]);

        $transactions = BasicTransaction::forCategory($category1->id)->get();

        $this->assertTrue($transactions->contains($transaction1));
        $this->assertFalse($transactions->contains($transaction2));
    }

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

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

        $category = $this->createIncomeCategory(['created_by_id' => $admin->id]);
        $transaction = $this->createIncomeTransaction([
            'category_id' => $category->id,
            'created_by_id' => $admin->id,
        ]);

        $this->assertInstanceOf(BasicTransactionCategory::class, $transaction->category);
        $this->assertEquals($category->id, $transaction->category->id);
    }

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

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

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

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

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

        $transaction = $this->createIncomeTransaction([
            'amount' => 1234.56,
            'created_by_id' => $admin->id,
        ]);

        // formatted_amount uses FormattingHelper::formatCurrency
        $this->assertNotNull($transaction->formatted_amount);
        $this->assertIsString($transaction->formatted_amount);
    }

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

        $transaction = $this->createIncomeTransaction([
            'transaction_date' => '2026-01-15',
            'created_by_id' => $admin->id,
        ]);

        $this->assertEquals('Jan 15, 2026', $transaction->formatted_date);
    }

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

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

        $badge = $transaction->type_badge;

        $this->assertStringContainsString('bg-label-success', $badge);
        $this->assertStringContainsString('Income', $badge);
    }

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

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

        $badge = $transaction->type_badge;

        $this->assertStringContainsString('bg-label-danger', $badge);
        $this->assertStringContainsString('Expense', $badge);
    }

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

        $transaction = $this->createIncomeTransaction([
            'payment_method' => 'credit_card',
            'created_by_id' => $admin->id,
        ]);

        $badge = $transaction->payment_method_badge;

        $this->assertStringContainsString('Credit Card', $badge);
        $this->assertStringContainsString('bg-label-primary', $badge);
    }

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

        $transaction = $this->createIncomeTransaction([
            'payment_method' => null,
            'created_by_id' => $admin->id,
        ]);

        $this->assertEquals('', $transaction->payment_method_badge);
    }

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

        $transaction = $this->createIncomeTransaction([
            'tags' => ['urgent', 'sales'],
            'created_by_id' => $admin->id,
        ]);

        $badges = $transaction->tags_badges;

        $this->assertStringContainsString('urgent', $badges);
        $this->assertStringContainsString('sales', $badges);
        $this->assertStringContainsString('bg-label-secondary', $badges);
    }

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

        $transaction = $this->createIncomeTransaction([
            'tags' => null,
            'created_by_id' => $admin->id,
        ]);

        $this->assertEquals('', $transaction->tags_badges);
    }

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

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

        // Create transactions
        $this->createIncomeTransaction([
            'amount' => 1000,
            'transaction_date' => now()->subDays(5),
            'created_by_id' => $admin->id,
        ]);
        $this->createIncomeTransaction([
            'amount' => 500,
            'transaction_date' => now()->subDays(3),
            'created_by_id' => $admin->id,
        ]);
        $this->createExpenseTransaction([
            'amount' => 300,
            'transaction_date' => now()->subDays(2),
            'created_by_id' => $admin->id,
        ]);

        $balance = BasicTransaction::getRunningBalance();

        $this->assertEquals(1500, $balance['income']);
        $this->assertEquals(300, $balance['expense']);
        $this->assertEquals(1200, $balance['balance']);
    }

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

        $this->createIncomeTransaction([
            'amount' => 1000,
            'transaction_date' => now()->subDays(10),
            'created_by_id' => $admin->id,
        ]);
        $this->createIncomeTransaction([
            'amount' => 500,
            'transaction_date' => now()->subDays(3),
            'created_by_id' => $admin->id,
        ]);

        // Get balance as of 5 days ago (should only include first transaction)
        $balance = BasicTransaction::getRunningBalance(now()->subDays(5));

        $this->assertEquals(1000, $balance['income']);
    }

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

        $startDate = now()->subDays(30);
        $endDate = now();

        // Create transactions within period
        $this->createIncomeTransaction([
            'amount' => 1000,
            'transaction_date' => now()->subDays(15),
            'created_by_id' => $admin->id,
        ]);
        $this->createIncomeTransaction([
            'amount' => 2000,
            'transaction_date' => now()->subDays(10),
            'created_by_id' => $admin->id,
        ]);
        $this->createExpenseTransaction([
            'amount' => 500,
            'transaction_date' => now()->subDays(5),
            'created_by_id' => $admin->id,
        ]);

        $summary = BasicTransaction::getSummaryForPeriod($startDate, $endDate);

        $this->assertArrayHasKey('income', $summary);
        $this->assertArrayHasKey('expense', $summary);
        $this->assertArrayHasKey('balance', $summary);
        $this->assertArrayHasKey('profit', $summary);
        $this->assertArrayHasKey('income_count', $summary);
        $this->assertArrayHasKey('expense_count', $summary);
        $this->assertArrayHasKey('total_count', $summary);

        $this->assertEquals(3000.0, $summary['income']);
        $this->assertEquals(500.0, $summary['expense']);
        $this->assertEquals(2500.0, $summary['balance']);
        $this->assertEquals(2, $summary['income_count']);
        $this->assertEquals(1, $summary['expense_count']);
        $this->assertEquals(3, $summary['total_count']);
    }

    public function test_get_summary_returns_zero_when_no_transactions(): void
    {
        $summary = BasicTransaction::getSummaryForPeriod(now()->subDays(30), now());

        $this->assertEquals(0.0, $summary['income']);
        $this->assertEquals(0.0, $summary['expense']);
        $this->assertEquals(0.0, $summary['balance']);
        $this->assertEquals(0, $summary['total_count']);
    }

    // ==========================================
    // Soft Delete Tests
    // ==========================================

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

        $transaction = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $transactionId = $transaction->id;

        $transaction->delete();

        $this->assertSoftDeleted('basic_transactions', ['id' => $transactionId]);
    }

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

        $transaction = $this->createIncomeTransaction(['created_by_id' => $admin->id]);
        $transaction->delete();
        $transaction->restore();

        $this->assertDatabaseHas('basic_transactions', [
            'id' => $transaction->id,
            'deleted_at' => null,
        ]);
    }
}
