"""
Tests for data migrations (initial accounts and fiscal period).

These tests verify that the data migrations create the correct
system accounts and fiscal period.
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from datetime import date
from dateutil.relativedelta import relativedelta
from django.apps import apps
import importlib

from accounting.models import Account, FiscalPeriod

User = get_user_model()


class InitialAccountsMigrationTest(TestCase):
    """
    Test the 0002_initial_accounts data migration.
    
    **Validates: Requirements 1.5, 1.9**
    """
    
    def setUp(self):
        """
        Set up test environment by running the migration function.
        """
        # Import the migration module dynamically
        migration_module = importlib.import_module('accounting.migrations.0002_initial_accounts')
        create_initial_accounts = migration_module.create_initial_accounts
        
        # Run the migration function
        create_initial_accounts(apps, None)
    
    def test_create_initial_accounts_creates_all_system_accounts(self):
        """
        Test that all 16 system accounts are created.
        
        Expected accounts:
        - 6 Asset accounts (202001-202005, 202010)
        - 2 Liability accounts (301001-301002)
        - 2 Equity accounts (350001-350002)
        - 2 Income accounts (401001-401002)
        - 4 Expense accounts (501001-501003, 502001)
        """
        # The migration should have already run, so we just verify
        system_accounts = Account.objects.filter(is_system_account=True)
        
        # Should have 16 system accounts
        self.assertEqual(system_accounts.count(), 16)
    
    def test_accounts_have_correct_codes_and_types(self):
        """
        Test that accounts have correct codes and account types.
        
        Validates account code assignment and type classification.
        """
        # Test Asset accounts
        loan_portfolio = Account.objects.get(code='202001')
        self.assertEqual(loan_portfolio.name, 'Loan Portfolio')
        self.assertEqual(loan_portfolio.account_type, 'asset')
        self.assertEqual(loan_portfolio.subtype, 'loan_portfolio')
        
        accrued_interest = Account.objects.get(code='202002')
        self.assertEqual(accrued_interest.name, 'Accrued Interest Receivable')
        self.assertEqual(accrued_interest.account_type, 'asset')
        self.assertEqual(accrued_interest.subtype, 'current_asset')
        
        cash_main = Account.objects.get(code='202003')
        self.assertEqual(cash_main.name, 'Cash - Main Branch')
        self.assertEqual(cash_main.account_type, 'asset')
        
        bank_main = Account.objects.get(code='202004')
        self.assertEqual(bank_main.name, 'Bank - Main Account')
        self.assertEqual(bank_main.account_type, 'asset')
        
        mpesa = Account.objects.get(code='202005')
        self.assertEqual(mpesa.name, 'M-Pesa Account')
        self.assertEqual(mpesa.account_type, 'asset')
        
        allowance = Account.objects.get(code='202010')
        self.assertEqual(allowance.name, 'Allowance for Loan Losses')
        self.assertEqual(allowance.account_type, 'asset')
        
        # Test Liability accounts
        client_savings = Account.objects.get(code='301001')
        self.assertEqual(client_savings.name, 'Client Savings Deposits')
        self.assertEqual(client_savings.account_type, 'liability')
        self.assertEqual(client_savings.subtype, 'client_savings')
        
        accrued_expenses = Account.objects.get(code='301002')
        self.assertEqual(accrued_expenses.name, 'Accrued Expenses Payable')
        self.assertEqual(accrued_expenses.account_type, 'liability')
        self.assertEqual(accrued_expenses.subtype, 'current_liability')
        
        # Test Equity accounts
        share_capital = Account.objects.get(code='350001')
        self.assertEqual(share_capital.name, 'Share Capital')
        self.assertEqual(share_capital.account_type, 'equity')
        
        retained_earnings = Account.objects.get(code='350002')
        self.assertEqual(retained_earnings.name, 'Retained Earnings')
        self.assertEqual(retained_earnings.account_type, 'equity')
        
        # Test Income accounts
        interest_income = Account.objects.get(code='401001')
        self.assertEqual(interest_income.name, 'Interest Income - Loans')
        self.assertEqual(interest_income.account_type, 'income')
        
        fee_income = Account.objects.get(code='401002')
        self.assertEqual(fee_income.name, 'Fee Income')
        self.assertEqual(fee_income.account_type, 'income')
        
        # Test Expense accounts
        operating_expenses = Account.objects.get(code='501001')
        self.assertEqual(operating_expenses.name, 'Operating Expenses')
        self.assertEqual(operating_expenses.account_type, 'expense')
        
        staff_salaries = Account.objects.get(code='501002')
        self.assertEqual(staff_salaries.name, 'Staff Salaries')
        self.assertEqual(staff_salaries.account_type, 'expense')
        
        loan_loss_provision = Account.objects.get(code='501003')
        self.assertEqual(loan_loss_provision.name, 'Loan Loss Provision Expense')
        self.assertEqual(loan_loss_provision.account_type, 'expense')
        
        depreciation = Account.objects.get(code='502001')
        self.assertEqual(depreciation.name, 'Depreciation Expense')
        self.assertEqual(depreciation.account_type, 'expense')
    
    def test_system_accounts_are_marked_correctly(self):
        """
        Test that all initial accounts are marked as system accounts.
        
        System accounts have is_system_account=True flag.
        """
        system_account_codes = [
            '202001', '202002', '202003', '202004', '202005', '202010',
            '301001', '301002',
            '350001', '350002',
            '401001', '401002',
            '501001', '501002', '501003', '502001',
        ]
        
        for code in system_account_codes:
            account = Account.objects.get(code=code)
            self.assertTrue(
                account.is_system_account,
                f"Account {code} should be marked as system account"
            )
            self.assertTrue(
                account.is_active,
                f"Account {code} should be active"
            )
    
    def test_all_accounts_have_descriptions(self):
        """
        Test that all system accounts have descriptions.
        
        Descriptions help users understand the purpose of each account.
        """
        system_accounts = Account.objects.filter(is_system_account=True)
        
        for account in system_accounts:
            self.assertTrue(
                account.description,
                f"Account {account.code} should have a description"
            )
            self.assertGreater(
                len(account.description),
                10,
                f"Account {account.code} description should be meaningful"
            )


class InitialFiscalPeriodMigrationTest(TestCase):
    """
    Test the 0003_initial_fiscal_period data migration.
    
    **Validates: Requirements 10.1, 10.2, 10.3**
    """
    
    def setUp(self):
        """
        Set up test environment by running the migration function.
        """
        # Import the migration module dynamically
        migration_module = importlib.import_module('accounting.migrations.0003_initial_fiscal_period')
        create_initial_fiscal_period = migration_module.create_initial_fiscal_period
        
        # Run the migration function
        create_initial_fiscal_period(apps, None)
    
    def test_initial_fiscal_period_is_created(self):
        """
        Test that an initial fiscal period is created for current month.
        """
        # Should have at least one fiscal period
        self.assertTrue(FiscalPeriod.objects.exists())
        
        # Get the fiscal period
        period = FiscalPeriod.objects.first()
        self.assertIsNotNone(period)
    
    def test_fiscal_period_has_correct_period_type(self):
        """
        Test that the fiscal period is set to 'monthly' type.
        """
        period = FiscalPeriod.objects.first()
        self.assertEqual(period.period_type, 'monthly')
    
    def test_fiscal_period_status_is_open(self):
        """
        Test that the initial fiscal period status is 'open'.
        
        An open period allows transactions to be recorded.
        """
        period = FiscalPeriod.objects.first()
        self.assertEqual(period.status, 'open')
    
    def test_fiscal_period_covers_current_month(self):
        """
        Test that the fiscal period covers the current month.
        """
        period = FiscalPeriod.objects.first()
        
        # Calculate current month boundaries
        today = date.today()
        expected_start = date(today.year, today.month, 1)
        
        # Calculate last day of current month
        if today.month == 12:
            expected_end = date(today.year, 12, 31)
        else:
            next_month_start = date(today.year, today.month + 1, 1)
            expected_end = next_month_start - relativedelta(days=1)
        
        self.assertEqual(period.start_date, expected_start)
        self.assertEqual(period.end_date, expected_end)
    
    def test_fiscal_period_has_correct_name(self):
        """
        Test that the fiscal period name follows format "Month YYYY".
        
        Example: "January 2024"
        """
        period = FiscalPeriod.objects.first()
        
        today = date.today()
        expected_name = today.strftime("%B %Y")
        
        self.assertEqual(period.name, expected_name)
    
    def test_fiscal_period_has_empty_balance_fields(self):
        """
        Test that opening and closing balances are initialized as empty dicts.
        """
        period = FiscalPeriod.objects.first()
        
        self.assertIsInstance(period.opening_balances, dict)
        self.assertIsInstance(period.closing_balances, dict)
        self.assertEqual(period.opening_balances, {})
        self.assertEqual(period.closing_balances, {})


class DataMigrationIntegrationTest(TestCase):
    """
    Integration tests verifying both migrations work together.
    """
    
    def setUp(self):
        """
        Set up test environment by running both migration functions.
        """
        # Import the migration modules dynamically
        migration_module_1 = importlib.import_module('accounting.migrations.0002_initial_accounts')
        create_initial_accounts = migration_module_1.create_initial_accounts
        
        migration_module_2 = importlib.import_module('accounting.migrations.0003_initial_fiscal_period')
        create_initial_fiscal_period = migration_module_2.create_initial_fiscal_period
        
        # Run both migration functions
        create_initial_accounts(apps, None)
        create_initial_fiscal_period(apps, None)
    
    def test_accounts_and_period_exist_together(self):
        """
        Test that both system accounts and fiscal period are available.
        
        This confirms the accounting system is ready for use.
        """
        # Verify accounts exist
        system_accounts_count = Account.objects.filter(is_system_account=True).count()
        self.assertEqual(system_accounts_count, 16)
        
        # Verify fiscal period exists
        self.assertTrue(FiscalPeriod.objects.exists())
        
        # Verify period is open
        open_period = FiscalPeriod.objects.filter(status='open').first()
        self.assertIsNotNone(open_period)
    
    def test_can_reference_accounts_by_code(self):
        """
        Test that key accounts can be looked up by their codes.
        
        This is important for integration service account mapping.
        """
        # These accounts are used by IntegrationService
        loan_portfolio = Account.objects.get(code='202001')
        self.assertIsNotNone(loan_portfolio)
        
        interest_income = Account.objects.get(code='401001')
        self.assertIsNotNone(interest_income)
        
        fee_income = Account.objects.get(code='401002')
        self.assertIsNotNone(fee_income)
        
        cash_account = Account.objects.get(code='202003')
        self.assertIsNotNone(cash_account)
        
        bank_account = Account.objects.get(code='202004')
        self.assertIsNotNone(bank_account)
        
        mpesa_account = Account.objects.get(code='202005')
        self.assertIsNotNone(mpesa_account)
