"""
Unit tests for ReportService.

Tests all financial report generation methods including Trial Balance,
Income Statement, Balance Sheet, Cash Flow Statement, and Account Analysis.
"""

from decimal import Decimal
from datetime import date, timedelta
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.utils import timezone

from accounting.models import (
    Account,
    JournalEntry,
    JournalEntryLine,
    GeneralLedger,
    FiscalPeriod,
)
from accounting.services.accounting_service import AccountingService
from accounting.services.report_service import ReportService
from users.models import Branch

User = get_user_model()


class ReportServiceTestCase(TestCase):
    """Test case for ReportService financial report generation."""

    def setUp(self):
        """Set up test data for report generation."""
        # Create test user
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )

        # Create test branch
        self.branch = Branch.objects.create(
            name='Test Branch',
            code='TB001'
        )

        # Create fiscal period
        self.fiscal_period = FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            status='open'
        )

        # Create test accounts
        self._create_test_accounts()

        # Initialize services
        self.accounting_service = AccountingService()
        self.report_service = ReportService()

        # Create test transactions
        self._create_test_transactions()

    def _create_test_accounts(self):
        """Create a complete set of test accounts."""
        # Asset accounts
        self.cash_account = Account.objects.create(
            code='202001',
            name='Cash',
            account_type='asset',
            subtype='current_asset',
            description='Cash on hand',
            is_active=True,
            created_by=self.user
        )

        self.bank_account = Account.objects.create(
            code='202002',
            name='Bank Account',
            account_type='asset',
            subtype='current_asset',
            description='Bank account',
            is_active=True,
            created_by=self.user
        )

        self.loan_portfolio = Account.objects.create(
            code='202003',
            name='Loan Portfolio',
            account_type='asset',
            subtype='loan_portfolio',
            description='Outstanding loans',
            is_active=True,
            created_by=self.user
        )

        self.fixed_asset = Account.objects.create(
            code='202010',
            name='Office Equipment',
            account_type='asset',
            subtype='fixed_asset',
            description='Office equipment',
            is_active=True,
            created_by=self.user
        )

        self.accounts_receivable = Account.objects.create(
            code='202004',
            name='Accounts Receivable',
            account_type='asset',
            subtype='current_asset',
            description='Amounts owed to us',
            is_active=True,
            created_by=self.user
        )

        # Liability accounts
        self.accounts_payable = Account.objects.create(
            code='303001',
            name='Accounts Payable',
            account_type='liability',
            subtype='current_liability',
            description='Amounts we owe',
            is_active=True,
            created_by=self.user
        )

        self.client_savings = Account.objects.create(
            code='303002',
            name='Client Savings',
            account_type='liability',
            subtype='client_savings',
            description='Client savings deposits',
            is_active=True,
            created_by=self.user
        )

        self.borrowings = Account.objects.create(
            code='303003',
            name='Bank Loan',
            account_type='liability',
            subtype='borrowings',
            description='Loan from bank',
            is_active=True,
            created_by=self.user
        )

        # Equity accounts
        self.capital = Account.objects.create(
            code='404001',
            name='Share Capital',
            account_type='equity',
            subtype=None,
            description='Owner equity',
            is_active=True,
            created_by=self.user
        )

        self.retained_earnings = Account.objects.create(
            code='404002',
            name='Retained Earnings',
            account_type='equity',
            subtype=None,
            description='Accumulated earnings',
            is_active=True,
            created_by=self.user
        )

        # Income accounts
        self.interest_income = Account.objects.create(
            code='501001',
            name='Interest Income',
            account_type='income',
            subtype=None,
            description='Interest from loans',
            is_active=True,
            created_by=self.user
        )

        self.fee_income = Account.objects.create(
            code='501002',
            name='Fee Income',
            account_type='income',
            subtype=None,
            description='Processing fees',
            is_active=True,
            created_by=self.user
        )

        # Expense accounts
        self.salary_expense = Account.objects.create(
            code='601001',
            name='Staff Salaries',
            account_type='expense',
            subtype=None,
            description='Employee salaries',
            is_active=True,
            created_by=self.user
        )

        self.rent_expense = Account.objects.create(
            code='601002',
            name='Office Rent',
            account_type='expense',
            subtype=None,
            description='Operating rent expense',
            is_active=True,
            created_by=self.user
        )

        self.depreciation_expense = Account.objects.create(
            code='601003',
            name='Depreciation Expense',
            account_type='expense',
            subtype=None,
            description='Depreciation on assets',
            is_active=True,
            created_by=self.user
        )

        self.provision_expense = Account.objects.create(
            code='601004',
            name='Loan Loss Provision',
            account_type='expense',
            subtype=None,
            description='Provision for loan losses',
            is_active=True,
            created_by=self.user
        )

    def _create_test_transactions(self):
        """Create test journal entries and post them."""
        # Transaction 1: Initial capital contribution
        je1 = JournalEntry.objects.create(
            reference_number='JE-2024-001',
            transaction_date=date(2024, 1, 1),
            description='Initial capital contribution',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je1,
            account=self.cash_account,
            description='Cash received',
            debit_amount=Decimal('100000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je1,
            account=self.capital,
            description='Share capital',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('100000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je1, self.user)

        # Transaction 2: Loan disbursement
        je2 = JournalEntry.objects.create(
            reference_number='JE-2024-002',
            transaction_date=date(2024, 1, 5),
            description='Loan disbursement',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je2,
            account=self.loan_portfolio,
            description='Loan disbursed',
            debit_amount=Decimal('50000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je2,
            account=self.cash_account,
            description='Cash paid',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('50000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je2, self.user)

        # Transaction 3: Interest income
        je3 = JournalEntry.objects.create(
            reference_number='JE-2024-003',
            transaction_date=date(2024, 1, 10),
            description='Interest income received',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je3,
            account=self.cash_account,
            description='Interest received',
            debit_amount=Decimal('5000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je3,
            account=self.interest_income,
            description='Interest income',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('5000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je3, self.user)

        # Transaction 4: Fee income
        je4 = JournalEntry.objects.create(
            reference_number='JE-2024-004',
            transaction_date=date(2024, 1, 12),
            description='Processing fees',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je4,
            account=self.cash_account,
            description='Fees received',
            debit_amount=Decimal('1000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je4,
            account=self.fee_income,
            description='Fee income',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('1000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je4, self.user)

        # Transaction 5: Salary expense
        je5 = JournalEntry.objects.create(
            reference_number='JE-2024-005',
            transaction_date=date(2024, 1, 15),
            description='Staff salaries',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je5,
            account=self.salary_expense,
            description='Salaries paid',
            debit_amount=Decimal('3000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je5,
            account=self.cash_account,
            description='Cash paid',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('3000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je5, self.user)

        # Transaction 6: Rent expense
        je6 = JournalEntry.objects.create(
            reference_number='JE-2024-006',
            transaction_date=date(2024, 1, 20),
            description='Office rent',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je6,
            account=self.rent_expense,
            description='Rent paid',
            debit_amount=Decimal('2000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je6,
            account=self.cash_account,
            description='Cash paid',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('2000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je6, self.user)

        # Transaction 7: Purchase fixed asset (before depreciation)
        je7 = JournalEntry.objects.create(
            reference_number='JE-2024-007',
            transaction_date=date(2024, 1, 25),
            description='Purchase office equipment',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je7,
            account=self.fixed_asset,
            description='Equipment purchased',
            debit_amount=Decimal('10000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je7,
            account=self.cash_account,
            description='Cash paid',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('10000.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je7, self.user)

        # Transaction 8: Depreciation (non-cash, after purchase)
        je8 = JournalEntry.objects.create(
            reference_number='JE-2024-008',
            transaction_date=date(2024, 1, 31),
            description='Monthly depreciation',
            branch=self.branch,
            created_by=self.user,
            status='draft'
        )
        JournalEntryLine.objects.create(
            journal_entry=je8,
            account=self.depreciation_expense,
            description='Depreciation',
            debit_amount=Decimal('500.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        JournalEntryLine.objects.create(
            journal_entry=je8,
            account=self.fixed_asset,
            description='Accumulated depreciation',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('500.00'),
            line_number=2
        )
        self.accounting_service.post_journal_entry(je8, self.user)

    def test_generate_trial_balance_returns_balanced_report(self):
        """Test that trial balance report is balanced."""
        report = self.report_service.generate_trial_balance(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify structure
        self.assertIn('report_date', report)
        self.assertIn('branch', report)
        self.assertIn('accounts', report)
        self.assertIn('totals', report)
        self.assertIn('by_type', report)

        # Debug output
        if not report['totals']['balanced']:
            print(f"\nDEBUG: Trial Balance Not Balanced!")
            print(f"Total Debits: {report['totals']['total_debits']}")
            print(f"Total Credits: {report['totals']['total_credits']}")
            print(f"Difference: {report['totals']['total_debits'] - report['totals']['total_credits']}")
            print("\nAccounts:")
            for acc in report['accounts']:
                print(f"  {acc['code']} {acc['name']}: DR={acc['debit_balance']}, CR={acc['credit_balance']}")

        # Verify balanced
        self.assertTrue(report['totals']['balanced'])
        self.assertEqual(
            report['totals']['total_debits'],
            report['totals']['total_credits']
        )

        # Verify accounts are present
        self.assertGreater(len(report['accounts']), 0)

    def test_generate_income_statement_calculates_net_income_correctly(self):
        """Test that income statement calculates net income correctly."""
        report = self.report_service.generate_income_statement(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify structure
        self.assertIn('period', report)
        self.assertIn('branch', report)
        self.assertIn('income', report)
        self.assertIn('expenses', report)
        self.assertIn('totals', report)

        # Verify income groups
        self.assertIn('interest_income', report['income'])
        self.assertIn('fee_income', report['income'])
        self.assertIn('other_income', report['income'])

        # Verify expense groups
        self.assertIn('operating_expenses', report['expenses'])
        self.assertIn('staff_costs', report['expenses'])
        self.assertIn('loan_loss_provisions', report['expenses'])
        self.assertIn('other_expenses', report['expenses'])

        # Verify totals
        # Interest: 5000, Fees: 1000 = Gross Income: 6000
        # Salary: 3000, Rent: 2000, Depreciation: 500 = Total Expenses: 5500
        # Net Income: 500
        self.assertEqual(report['totals']['gross_income'], Decimal('6000.00'))
        self.assertEqual(report['totals']['total_expenses'], Decimal('5500.00'))
        self.assertEqual(report['totals']['net_income'], Decimal('500.00'))

    def test_generate_balance_sheet_verifies_accounting_equation(self):
        """Test that balance sheet verifies the accounting equation."""
        report = self.report_service.generate_balance_sheet(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify structure
        self.assertIn('as_of_date', report)
        self.assertIn('branch', report)
        self.assertIn('assets', report)
        self.assertIn('liabilities', report)
        self.assertIn('equity', report)
        self.assertIn('totals', report)

        # Verify asset subtypes
        self.assertIn('current_asset', report['assets'])
        self.assertIn('loan_portfolio', report['assets'])
        self.assertIn('fixed_asset', report['assets'])
        self.assertIn('other_asset', report['assets'])

        # Verify liability subtypes
        self.assertIn('current_liability', report['liabilities'])
        self.assertIn('client_savings', report['liabilities'])
        self.assertIn('borrowings', report['liabilities'])
        self.assertIn('other_liability', report['liabilities'])

        # Verify accounting equation: Assets = Liabilities + Equity
        self.assertTrue(report['totals']['balanced'])
        self.assertEqual(
            report['totals']['total_assets'],
            report['totals']['total_liabilities'] + report['totals']['total_equity']
        )

        # Verify equity includes net income
        equity_accounts = report['equity']['accounts']
        has_net_income = any(
            acc['code'] == 'NET_INCOME' for acc in equity_accounts
        )
        self.assertTrue(has_net_income)

    def test_generate_cash_flow_statement_reconciles_cash_balances(self):
        """Test that cash flow statement reconciles beginning and ending cash."""
        report = self.report_service.generate_cash_flow_statement(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Debug output
        print(f"\n=== CASH FLOW DEBUG ===")
        print(f"Beginning Cash: {report['beginning_cash']}")
        print(f"Net Change: {report['net_change']}")
        print(f"Calculated Ending: {report['calculated_ending_cash']}")
        print(f"Actual Ending: {report['ending_cash']}")
        print(f"Difference: {abs(report['ending_cash'] - report['calculated_ending_cash'])}")
        print(f"\nOperating Activities:")
        print(f"  Net Income: {report['operating_activities']['net_income']}")
        print(f"  Adjustments: {report['operating_activities']['adjustments']}")
        print(f"  WC Changes: {report['operating_activities']['working_capital_changes']}")
        print(f"  Total: {report['operating_activities']['total']}")
        print(f"\nInvesting Activities: {report['investing_activities']}")
        print(f"\nFinancing Activities: {report['financing_activities']}")

        # Verify structure
        self.assertIn('period', report)
        self.assertIn('branch', report)
        self.assertIn('operating_activities', report)
        self.assertIn('investing_activities', report)
        self.assertIn('financing_activities', report)
        self.assertIn('net_change', report)
        self.assertIn('beginning_cash', report)
        self.assertIn('ending_cash', report)

        # Verify operating activities starts with net income
        self.assertIn('net_income', report['operating_activities'])
        self.assertEqual(
            report['operating_activities']['net_income'],
            Decimal('500.00')
        )

        # Verify depreciation is added back
        self.assertIn('adjustments', report['operating_activities'])
        adjustments = report['operating_activities']['adjustments']
        depreciation_found = any(
            'Depreciation' in adj['description'] for adj in adjustments
        )
        self.assertTrue(depreciation_found)

        # Verify reconciliation
        calculated_ending = report['beginning_cash'] + report['net_change']
        self.assertEqual(report['calculated_ending_cash'], calculated_ending)
        
        # Allow small rounding differences
        difference = abs(report['ending_cash'] - calculated_ending)
        self.assertLess(difference, Decimal('0.01'))

    def test_generate_account_analysis_shows_transaction_history(self):
        """Test that account analysis shows complete transaction history."""
        report = self.report_service.generate_account_analysis(
            account=self.cash_account,
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31)
        )

        # Verify structure
        self.assertIn('account', report)
        self.assertIn('period', report)
        self.assertIn('opening_balance', report)
        self.assertIn('transactions', report)
        self.assertIn('totals', report)
        self.assertIn('closing_balance', report)

        # Verify account info
        self.assertEqual(report['account']['code'], self.cash_account.code)
        self.assertEqual(report['account']['name'], self.cash_account.name)

        # Verify transactions are present
        self.assertGreater(len(report['transactions']), 0)

        # Verify each transaction has required fields
        for txn in report['transactions']:
            self.assertIn('date', txn)
            self.assertIn('description', txn)
            self.assertIn('reference', txn)
            self.assertIn('debit', txn)
            self.assertIn('credit', txn)
            self.assertIn('balance', txn)

        # Verify totals are calculated
        self.assertIn('total_debits', report['totals'])
        self.assertIn('total_credits', report['totals'])
        self.assertIn('net_change', report['totals'])

        # Verify closing balance is calculated correctly
        expected_closing = (
            report['opening_balance'] + 
            report['totals']['net_change']
        )
        self.assertEqual(report['closing_balance'], expected_closing)

    def test_trial_balance_caching_works(self):
        """Test that trial balance caching improves performance."""
        # First call - not cached
        report1 = self.report_service.generate_trial_balance(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Second call - should be cached
        report2 = self.report_service.generate_trial_balance(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Reports should be identical
        self.assertEqual(report1['totals'], report2['totals'])
        self.assertEqual(len(report1['accounts']), len(report2['accounts']))

    def test_income_statement_groups_correctly(self):
        """Test that income statement groups accounts correctly."""
        report = self.report_service.generate_income_statement(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify interest income is grouped correctly
        interest_group = report['income']['interest_income']
        self.assertGreater(interest_group['total'], Decimal('0.00'))
        interest_accounts = [
            acc for acc in interest_group['accounts']
            if 'Interest' in acc['name']
        ]
        self.assertGreater(len(interest_accounts), 0)

        # Verify fee income is grouped correctly
        fee_group = report['income']['fee_income']
        self.assertGreater(fee_group['total'], Decimal('0.00'))
        fee_accounts = [
            acc for acc in fee_group['accounts']
            if 'Fee' in acc['name']
        ]
        self.assertGreater(len(fee_accounts), 0)

        # Verify staff costs are grouped correctly
        staff_group = report['expenses']['staff_costs']
        self.assertGreater(staff_group['total'], Decimal('0.00'))
        staff_accounts = [
            acc for acc in staff_group['accounts']
            if 'Staff' in acc['name'] or 'Salaries' in acc['name']
        ]
        self.assertGreater(len(staff_accounts), 0)

    def test_balance_sheet_groups_by_subtype(self):
        """Test that balance sheet groups accounts by subtype correctly."""
        report = self.report_service.generate_balance_sheet(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify current assets are present
        current_assets = report['assets']['current_asset']
        self.assertGreater(len(current_assets['accounts']), 0)
        self.assertGreater(current_assets['total'], Decimal('0.00'))

        # Verify loan portfolio is present
        loan_portfolio = report['assets']['loan_portfolio']
        self.assertGreater(len(loan_portfolio['accounts']), 0)
        self.assertGreater(loan_portfolio['total'], Decimal('0.00'))

        # Verify fixed assets are present (if we added equipment)
        fixed_assets = report['assets']['fixed_asset']
        # May be zero or negative due to depreciation, but should have accounts
        self.assertGreaterEqual(len(fixed_assets['accounts']), 0)

    def test_cash_flow_identifies_non_cash_expenses(self):
        """Test that cash flow correctly identifies and adds back non-cash expenses."""
        report = self.report_service.generate_cash_flow_statement(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Verify depreciation is in adjustments
        adjustments = report['operating_activities']['adjustments']
        depreciation_adj = [
            adj for adj in adjustments
            if 'Depreciation' in adj['description']
        ]
        self.assertEqual(len(depreciation_adj), 1)
        self.assertEqual(depreciation_adj[0]['amount'], Decimal('500.00'))

    def test_account_analysis_with_no_transactions(self):
        """Test account analysis for account with no transactions."""
        # Create new account with no transactions
        empty_account = Account.objects.create(
            code='999999',
            name='Empty Account',
            account_type='asset',
            subtype='other_asset',
            description='Test empty account',
            is_active=True,
            created_by=self.user
        )

        report = self.report_service.generate_account_analysis(
            account=empty_account,
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31)
        )

        # Verify empty results
        self.assertEqual(report['opening_balance'], Decimal('0.00'))
        self.assertEqual(len(report['transactions']), 0)
        self.assertEqual(report['closing_balance'], Decimal('0.00'))
        self.assertEqual(report['totals']['total_debits'], Decimal('0.00'))
        self.assertEqual(report['totals']['total_credits'], Decimal('0.00'))

    def test_reports_filter_by_branch(self):
        """Test that reports correctly filter by branch."""
        # Create second branch
        branch2 = Branch.objects.create(
            name='Branch 2',
            code='B002'
        )

        # Generate reports for specific branch
        tb_report = self.report_service.generate_trial_balance(
            as_of_date=date(2024, 1, 31),
            branch=self.branch
        )

        # Branch name should be in report
        self.assertEqual(tb_report['branch'], self.branch.name)

        # Generate for all branches (no filter)
        tb_all = self.report_service.generate_trial_balance(
            as_of_date=date(2024, 1, 31),
            branch=None
        )

        self.assertEqual(tb_all['branch'], 'All Branches')
