#!/usr/bin/env python3
"""
GL Account Routing Test Suite

This test script validates that purchase items are correctly routed to GL accounts
based on their item type, not hardcoded to a single account.

Tests:
1. Vehicle purchase → GL 1210 (Motor Vehicles)
2. Machinery purchase → GL 1205 (Machinery)
3. Office supplies → GL 5020 (Supplies Expense)
4. Raw materials → GL 1115 (Raw Materials)
5. Mixed basket with multiple item types
"""

import sys
import uuid
from datetime import date
from decimal import Decimal

# Add parent directory to path
sys.path.insert(0, '/'.join(__file__.split('/')[:-2]))

from app.core.database import SessionLocal
from app.models.purchases import Purchase, PurchaseItem, Supplier
from app.models.inventory import Product
from app.models.branch import Branch
from app.models.accounting import AccountingCode, AccountingEntry, JournalEntry
from app.models.item_type_mapping import ItemTypeDefinition
from app.services.purchase_service import PurchaseService
from app.services.gl_account_resolver import GLAccountResolver


class GLAccountRoutingTester:
    """Test GL account routing for purchase items"""

    def __init__(self):
        self.db = SessionLocal()
        self.service = PurchaseService(self.db)
        self.resolver = GLAccountResolver(self.db)
        self.test_results = []

    def run_all_tests(self):
        """Run all routing tests"""
        print("\n" + "="*80)
        print("GL ACCOUNT ROUTING TEST SUITE")
        print("="*80)

        try:
            # Setup test data
            self._setup_test_data()

            # Run specific routing tests
            print("\n[TEST 1] Vehicle Purchase Routing")
            self._test_vehicle_routing()

            print("\n[TEST 2] Machinery Purchase Routing")
            self._test_machinery_routing()

            print("\n[TEST 3] Office Supplies Routing")
            self._test_supplies_routing()

            print("\n[TEST 4] Raw Materials Routing")
            self._test_raw_materials_routing()

            print("\n[TEST 5] Mixed Basket Routing")
            self._test_mixed_basket_routing()

            # Print summary
            self._print_summary()

        except Exception as e:
            print(f"\n✗ TEST ERROR: {str(e)}")
            import traceback
            traceback.print_exc()
            return False

        finally:
            self.db.close()

        return all(result['passed'] for result in self.test_results)

    def _setup_test_data(self):
        """Create test GL accounts and products"""
        print("\nSetting up test data...")

        # Ensure we have required GL accounts
        self._ensure_gl_account('1205', 'Machinery', 'Asset')
        self._ensure_gl_account('1210', 'Motor Vehicles', 'Asset')
        self._ensure_gl_account('1115', 'Raw Materials', 'Asset')
        self._ensure_gl_account('5020', 'Office Supplies', 'Expense')
        self._ensure_gl_account('1162', 'Input VAT', 'Asset')
        self._ensure_gl_account('1000', 'Accounts Payable', 'Liability')

        # Create test branch
        self.branch = self.db.query(Branch).first()
        if not self.branch:
            self.branch = Branch(
                id=str(uuid.uuid4()),
                name="Test Branch",
                code="TEST",
                location="Test Location"
            )
            self.db.add(self.branch)
            self.db.flush()

        # Create test supplier
        ap_account = self.db.query(AccountingCode).filter(
            AccountingCode.code == '1000'
        ).first()

        self.supplier = Supplier(
            id=str(uuid.uuid4()),
            name="Test Supplier",
            accounting_code_id=ap_account.id,
            branch_id=self.branch.id
        )
        self.db.add(self.supplier)
        self.db.flush()

        print("✓ Test data setup complete")

    def _ensure_gl_account(self, code, name, account_type):
        """Ensure a GL account exists"""
        account = self.db.query(AccountingCode).filter(
            AccountingCode.code == code
        ).first()

        if not account:
            account = AccountingCode(
                id=str(uuid.uuid4()),
                code=code,
                name=name,
                account_type=account_type,
                category=account_type,
                is_active=True
            )
            self.db.add(account)
            print(f"  Created GL account: {code} - {name}")

        return account

    def _get_or_create_product(self, name):
        """Get or create a test product"""
        product = self.db.query(Product).filter(
            Product.name == name
        ).first()

        if not product:
            product = Product(
                id=str(uuid.uuid4()),
                name=name,
                branch_id=self.branch.id,
                selling_price=1000,
                purchase_price=500,
                product_type='inventory_item'
            )
            self.db.add(product)
            self.db.flush()

        return product

    def _get_item_type_definition(self, item_type):
        """Get or create item type definition"""
        item_type_def = self.db.query(ItemTypeDefinition).filter(
            ItemTypeDefinition.item_type == item_type
        ).first()

        return item_type_def

    def _test_vehicle_routing(self):
        """Test that vehicle purchases route to GL 1210"""
        try:
            # Get or create vehicle product
            vehicle_product = self._get_or_create_product("Company Vehicle")

            # Get vehicle item type
            vehicle_item_type = self._get_item_type_definition('vehicle')
            if not vehicle_item_type:
                print("  ⚠ Vehicle item type not found - skipping test")
                self.test_results.append({
                    'name': 'Vehicle Routing',
                    'passed': False,
                    'reason': 'Item type not seeded'
                })
                return

            # Create purchase with vehicle
            purchase = Purchase(
                id=str(uuid.uuid4()),
                supplier_id=self.supplier.id,
                purchase_date=date.today(),
                total_amount=Decimal('50000'),
                total_vat_amount=Decimal('0'),
                total_amount_ex_vat=Decimal('50000'),
                status='pending',
                branch_id=self.branch.id,
                reference='VEH-001',
                payment_method='credit'
            )
            self.db.add(purchase)
            self.db.flush()

            # Create purchase item with vehicle type
            purchase_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=vehicle_product.id,
                quantity=Decimal('1'),
                cost=Decimal('50000'),
                total_cost=Decimal('50000'),
                item_type_id=vehicle_item_type.id,
                is_inventory=False,
                is_asset=True
            )
            self.db.add(purchase_item)
            self.db.flush()

            # Resolve GL account
            gl_account = self.resolver.get_debit_account_for_item(purchase_item, self.supplier)

            # Verify it's GL 1210 (Motor Vehicles)
            success = gl_account.code == '1210'
            message = f"Vehicle routed to {gl_account.code} ({gl_account.name})"

            if success:
                print(f"  ✓ {message}")
            else:
                print(f"  ✗ {message} - Expected GL 1210")

            self.test_results.append({
                'name': 'Vehicle Routing',
                'passed': success,
                'expected': 'GL 1210 (Motor Vehicles)',
                'actual': f"GL {gl_account.code} ({gl_account.name})"
            })

            self.db.rollback()

        except Exception as e:
            print(f"  ✗ Error: {str(e)}")
            self.test_results.append({
                'name': 'Vehicle Routing',
                'passed': False,
                'reason': str(e)
            })

    def _test_machinery_routing(self):
        """Test that machinery purchases route to GL 1205"""
        try:
            # Get or create machinery product
            machinery_product = self._get_or_create_product("Industrial Machinery")

            # Get machinery item type
            machinery_item_type = self._get_item_type_definition('machinery')
            if not machinery_item_type:
                print("  ⚠ Machinery item type not found - skipping test")
                self.test_results.append({
                    'name': 'Machinery Routing',
                    'passed': False,
                    'reason': 'Item type not seeded'
                })
                return

            # Create purchase with machinery
            purchase = Purchase(
                id=str(uuid.uuid4()),
                supplier_id=self.supplier.id,
                purchase_date=date.today(),
                total_amount=Decimal('100000'),
                total_vat_amount=Decimal('0'),
                total_amount_ex_vat=Decimal('100000'),
                status='pending',
                branch_id=self.branch.id,
                reference='MAC-001',
                payment_method='credit'
            )
            self.db.add(purchase)
            self.db.flush()

            # Create purchase item with machinery type
            purchase_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=machinery_product.id,
                quantity=Decimal('1'),
                cost=Decimal('100000'),
                total_cost=Decimal('100000'),
                item_type_id=machinery_item_type.id,
                is_inventory=False,
                is_asset=True
            )
            self.db.add(purchase_item)
            self.db.flush()

            # Resolve GL account
            gl_account = self.resolver.get_debit_account_for_item(purchase_item, self.supplier)

            # Verify it's GL 1205 (Machinery)
            success = gl_account.code == '1205'
            message = f"Machinery routed to {gl_account.code} ({gl_account.name})"

            if success:
                print(f"  ✓ {message}")
            else:
                print(f"  ✗ {message} - Expected GL 1205")

            self.test_results.append({
                'name': 'Machinery Routing',
                'passed': success,
                'expected': 'GL 1205 (Machinery)',
                'actual': f"GL {gl_account.code} ({gl_account.name})"
            })

            self.db.rollback()

        except Exception as e:
            print(f"  ✗ Error: {str(e)}")
            self.test_results.append({
                'name': 'Machinery Routing',
                'passed': False,
                'reason': str(e)
            })

    def _test_supplies_routing(self):
        """Test that office supplies route to GL 5020"""
        try:
            # Get or create supplies product
            supplies_product = self._get_or_create_product("Office Supplies")

            # Get supplies item type
            supplies_item_type = self._get_item_type_definition('supplies')
            if not supplies_item_type:
                print("  ⚠ Supplies item type not found - skipping test")
                self.test_results.append({
                    'name': 'Supplies Routing',
                    'passed': False,
                    'reason': 'Item type not seeded'
                })
                return

            # Create purchase with supplies
            purchase = Purchase(
                id=str(uuid.uuid4()),
                supplier_id=self.supplier.id,
                purchase_date=date.today(),
                total_amount=Decimal('500'),
                total_vat_amount=Decimal('0'),
                total_amount_ex_vat=Decimal('500'),
                status='pending',
                branch_id=self.branch.id,
                reference='SUP-001',
                payment_method='credit'
            )
            self.db.add(purchase)
            self.db.flush()

            # Create purchase item with supplies type
            purchase_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=supplies_product.id,
                quantity=Decimal('1'),
                cost=Decimal('500'),
                total_cost=Decimal('500'),
                item_type_id=supplies_item_type.id,
                is_inventory=False,
                is_asset=False
            )
            self.db.add(purchase_item)
            self.db.flush()

            # Resolve GL account
            gl_account = self.resolver.get_debit_account_for_item(purchase_item, self.supplier)

            # Verify it's GL 5020 (Office Supplies)
            success = gl_account.code == '5020'
            message = f"Supplies routed to {gl_account.code} ({gl_account.name})"

            if success:
                print(f"  ✓ {message}")
            else:
                print(f"  ✗ {message} - Expected GL 5020")

            self.test_results.append({
                'name': 'Supplies Routing',
                'passed': success,
                'expected': 'GL 5020 (Office Supplies)',
                'actual': f"GL {gl_account.code} ({gl_account.name})"
            })

            self.db.rollback()

        except Exception as e:
            print(f"  ✗ Error: {str(e)}")
            self.test_results.append({
                'name': 'Supplies Routing',
                'passed': False,
                'reason': str(e)
            })

    def _test_raw_materials_routing(self):
        """Test that raw materials route to GL 1115"""
        try:
            # Get or create raw materials product
            raw_materials_product = self._get_or_create_product("Raw Materials")

            # Get raw_materials item type
            raw_materials_item_type = self._get_item_type_definition('raw_materials')
            if not raw_materials_item_type:
                print("  ⚠ Raw materials item type not found - skipping test")
                self.test_results.append({
                    'name': 'Raw Materials Routing',
                    'passed': False,
                    'reason': 'Item type not seeded'
                })
                return

            # Create purchase with raw materials
            purchase = Purchase(
                id=str(uuid.uuid4()),
                supplier_id=self.supplier.id,
                purchase_date=date.today(),
                total_amount=Decimal('5000'),
                total_vat_amount=Decimal('0'),
                total_amount_ex_vat=Decimal('5000'),
                status='pending',
                branch_id=self.branch.id,
                reference='RM-001',
                payment_method='credit'
            )
            self.db.add(purchase)
            self.db.flush()

            # Create purchase item with raw materials type
            purchase_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=raw_materials_product.id,
                quantity=Decimal('100'),
                cost=Decimal('50'),
                total_cost=Decimal('5000'),
                item_type_id=raw_materials_item_type.id,
                is_inventory=True,
                is_asset=False
            )
            self.db.add(purchase_item)
            self.db.flush()

            # Resolve GL account
            gl_account = self.resolver.get_debit_account_for_item(purchase_item, self.supplier)

            # Verify it's GL 1115 (Raw Materials)
            success = gl_account.code == '1115'
            message = f"Raw materials routed to {gl_account.code} ({gl_account.name})"

            if success:
                print(f"  ✓ {message}")
            else:
                print(f"  ✗ {message} - Expected GL 1115")

            self.test_results.append({
                'name': 'Raw Materials Routing',
                'passed': success,
                'expected': 'GL 1115 (Raw Materials)',
                'actual': f"GL {gl_account.code} ({gl_account.name})"
            })

            self.db.rollback()

        except Exception as e:
            print(f"  ✗ Error: {str(e)}")
            self.test_results.append({
                'name': 'Raw Materials Routing',
                'passed': False,
                'reason': str(e)
            })

    def _test_mixed_basket_routing(self):
        """Test mixed purchase with multiple item types"""
        try:
            print("  Creating mixed purchase with vehicle + machinery + supplies...")

            # Get item types
            vehicle_type = self._get_item_type_definition('vehicle')
            machinery_type = self._get_item_type_definition('machinery')
            supplies_type = self._get_item_type_definition('supplies')

            if not (vehicle_type and machinery_type and supplies_type):
                print("  ⚠ Some item types not found - skipping test")
                self.test_results.append({
                    'name': 'Mixed Basket Routing',
                    'passed': False,
                    'reason': 'Not all item types seeded'
                })
                return

            # Create purchase
            purchase = Purchase(
                id=str(uuid.uuid4()),
                supplier_id=self.supplier.id,
                purchase_date=date.today(),
                total_amount=Decimal('150500'),
                total_vat_amount=Decimal('0'),
                total_amount_ex_vat=Decimal('150500'),
                status='pending',
                branch_id=self.branch.id,
                reference='MIX-001',
                payment_method='credit'
            )
            self.db.add(purchase)
            self.db.flush()

            # Create vehicle item
            vehicle_product = self._get_or_create_product("Vehicle for Mix")
            vehicle_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=vehicle_product.id,
                quantity=Decimal('1'),
                cost=Decimal('50000'),
                total_cost=Decimal('50000'),
                item_type_id=vehicle_type.id,
                is_inventory=False,
                is_asset=True
            )
            self.db.add(vehicle_item)

            # Create machinery item
            machinery_product = self._get_or_create_product("Machinery for Mix")
            machinery_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=machinery_product.id,
                quantity=Decimal('1'),
                cost=Decimal('100000'),
                total_cost=Decimal('100000'),
                item_type_id=machinery_type.id,
                is_inventory=False,
                is_asset=True
            )
            self.db.add(machinery_item)

            # Create supplies item
            supplies_product = self._get_or_create_product("Supplies for Mix")
            supplies_item = PurchaseItem(
                id=str(uuid.uuid4()),
                purchase_id=purchase.id,
                product_id=supplies_product.id,
                quantity=Decimal('1'),
                cost=Decimal('500'),
                total_cost=Decimal('500'),
                item_type_id=supplies_type.id,
                is_inventory=False,
                is_asset=False
            )
            self.db.add(supplies_item)
            self.db.flush()

            # Resolve GL accounts for each
            vehicle_gl = self.resolver.get_debit_account_for_item(vehicle_item, self.supplier)
            machinery_gl = self.resolver.get_debit_account_for_item(machinery_item, self.supplier)
            supplies_gl = self.resolver.get_debit_account_for_item(supplies_item, self.supplier)

            # Verify routing
            vehicle_ok = vehicle_gl.code == '1210'
            machinery_ok = machinery_gl.code == '1205'
            supplies_ok = supplies_gl.code == '5020'
            success = vehicle_ok and machinery_ok and supplies_ok

            print(f"  {'✓' if vehicle_ok else '✗'} Vehicle → {vehicle_gl.code}")
            print(f"  {'✓' if machinery_ok else '✗'} Machinery → {machinery_gl.code}")
            print(f"  {'✓' if supplies_ok else '✗'} Supplies → {supplies_gl.code}")

            self.test_results.append({
                'name': 'Mixed Basket Routing',
                'passed': success,
                'vehicle': f"{vehicle_gl.code} (expected 1210)",
                'machinery': f"{machinery_gl.code} (expected 1205)",
                'supplies': f"{supplies_gl.code} (expected 5020)"
            })

            self.db.rollback()

        except Exception as e:
            print(f"  ✗ Error: {str(e)}")
            self.test_results.append({
                'name': 'Mixed Basket Routing',
                'passed': False,
                'reason': str(e)
            })

    def _print_summary(self):
        """Print test summary"""
        print("\n" + "="*80)
        print("TEST RESULTS SUMMARY")
        print("="*80)

        passed = sum(1 for r in self.test_results if r['passed'])
        total = len(self.test_results)

        for result in self.test_results:
            status = "✓ PASS" if result['passed'] else "✗ FAIL"
            print(f"\n{status}: {result['name']}")

            if result['passed']:
                for key, value in result.items():
                    if key not in ['name', 'passed']:
                        print(f"  {key}: {value}")
            else:
                reason = result.get('reason', 'Unknown error')
                print(f"  Reason: {reason}")

        print("\n" + "="*80)
        print(f"TOTAL: {passed}/{total} tests passed")
        print("="*80 + "\n")

        return passed == total


def main():
    """Run the test suite"""
    tester = GLAccountRoutingTester()
    success = tester.run_all_tests()

    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
