"""
Full reset and dimensional accounting smoke test.

WARNING: This script drops and recreates the entire database.
Run only in a disposable or non-production environment.
"""

from decimal import Decimal
from datetime import date

from sqlalchemy import text, and_

from app.core.database import engine, SessionLocal
from app.models.base import Base
from app.models.branch import Branch
from app.models.accounting import AccountingCode
from app.models.banking import BankAccount
from app.models.accounting_dimensions import AccountingDimension, AccountingDimensionValue
from app.models.accounting_code_dimensions import AccountingCodeDimensionRequirement
from app.services.accounting_service import AccountingService
from scripts.seed_dimensions_and_defaults import main as seed_dimensions_and_defaults, ACCOUNT_DEFAULTS


def reset_database():
    """Drop all tables (with CASCADE) and recreate ORM-managed ones."""
    with engine.connect() as conn:
        # Get all table names and drop them with CASCADE
        conn.execute(text("""
            DO $$ DECLARE
                r RECORD;
            BEGIN
                FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public')
                LOOP
                    EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
                END LOOP;
            END $$;
        """))
        conn.commit()

    # Recreate all ORM-managed tables
    Base.metadata.create_all(bind=engine)


def seed_chart_of_accounts(db, branch_id):
    """Seed minimal accounts needed for the smoke test."""
    accounts = [
        # Assets
        {"code": "1100", "name": "Operating Bank", "account_type": "Asset", "category": "Current Asset"},
        {"code": "1200", "name": "Accounts Receivable", "account_type": "Asset", "category": "Current Asset"},
        {"code": "1300", "name": "Inventory - Main", "account_type": "Asset", "category": "Inventory"},
        {"code": "1310", "name": "Inventory - Branch B", "account_type": "Asset", "category": "Inventory"},
        {"code": "1400", "name": "Work in Process", "account_type": "Asset", "category": "Inventory"},
        {"code": "1500", "name": "Fixed Assets", "account_type": "Asset", "category": "Fixed Asset"},
        # Liabilities
        {"code": "2100", "name": "Accounts Payable", "account_type": "Liability", "category": "Current Liability"},
        {"code": "2105", "name": "Manufacturing Clearing", "account_type": "Liability", "category": "Other Liability"},
        # Equity
        {"code": "3000", "name": "Owner Equity", "account_type": "Equity", "category": "Equity"},
        # Revenue
        {"code": "4000", "name": "Product Sales", "account_type": "Revenue", "category": "Sales"},
        # Expenses
        {"code": "5000", "name": "Cost of Goods Sold", "account_type": "Expense", "category": "COGS"},
        {"code": "5200", "name": "Direct Labor", "account_type": "Expense", "category": "Payroll"},
        {"code": "6100", "name": "Rent Expense", "account_type": "Expense", "category": "Operating Expense"},
        {"code": "6200", "name": "Utilities Expense", "account_type": "Expense", "category": "Operating Expense"},
        {"code": "6300", "name": "Supplies Expense", "account_type": "Expense", "category": "Operating Expense"},
    ]

    for acct in accounts:
        existing = db.query(AccountingCode).filter(AccountingCode.code == acct["code"]).first()
        if existing:
            continue
        db.add(AccountingCode(branch_id=branch_id, **acct))
    db.flush()


def apply_dimension_requirements(db):
    """Apply dimension requirements to all accounts based on their type."""
    # Map dimension codes to their dimension objects
    dimensions = db.query(AccountingDimension).all()
    dim_by_code = {d.code: d for d in dimensions}

    # Get all accounts
    accounts = db.query(AccountingCode).all()

    for acct in accounts:
        defaults = ACCOUNT_DEFAULTS.get(acct.account_type, [])
        for dim_code, val_code in defaults:
            dim = dim_by_code.get(dim_code)
            if not dim:
                continue

            # Find the dimension value
            val = db.query(AccountingDimensionValue).filter(
                and_(
                    AccountingDimensionValue.dimension_id == dim.id,
                    AccountingDimensionValue.code == val_code,
                )
            ).first()
            if not val:
                continue

            # Check if requirement already exists
            req = db.query(AccountingCodeDimensionRequirement).filter(
                and_(
                    AccountingCodeDimensionRequirement.accounting_code_id == acct.id,
                    AccountingCodeDimensionRequirement.dimension_id == dim.id,
                )
            ).first()

            if req:
                # Update if needed
                req.default_dimension_value_id = val.id
                db.add(req)
            else:
                # Create new requirement
                req = AccountingCodeDimensionRequirement(
                    accounting_code_id=acct.id,
                    dimension_id=dim.id,
                    is_required=True,
                    default_dimension_value_id=val.id,
                    priority=1,
                    description="Seeded default",
                )
                db.add(req)

    db.flush()


def get_account_id(db, code: str) -> str:
    acct = db.query(AccountingCode).filter(AccountingCode.code == code).first()
    if not acct:
        raise ValueError(f"Account {code} not found")
    return acct.id


def post_journal(db, branch_id: str, particulars: str, book: str, lines):
    svc = AccountingService(db)
    entry_data = {
        "date_prepared": date.today(),
        "date_posted": date.today(),
        "particulars": particulars,
        "book": book,
        "entries": lines,
    }
    entry, result = svc.create_journal_entry(entry_data, branch_id)
    if not result.get("success"):
        raise RuntimeError(f"Journal failed: {result}")
    return entry


def seed_branches_and_bank(db):
    hq = Branch(name="Head Office", code="HQ", is_head_office=True, currency="USD")
    branch_b = Branch(name="Branch B", code="BRB", is_head_office=False, currency="USD")
    db.add(hq)
    db.add(branch_b)
    db.flush()

    # Link bank account to Operating Bank GL
    bank_gl_id = get_account_id(db, "1100")
    bank = BankAccount(name="Operating Bank", account_number="000123456", bank_name="Demo Bank", branch_id=hq.id, accounting_code_id=bank_gl_id)
    db.add(bank)
    db.flush()

    return hq.id, branch_b.id, bank.id


def run_smoke(db, hq_branch_id: str, branch_b_id: str):
    # Helpers to fetch ids
    bank = get_account_id(db, "1100")
    ar = get_account_id(db, "1200")
    inv_main = get_account_id(db, "1300")
    inv_b = get_account_id(db, "1310")
    wip = get_account_id(db, "1400")
    fixed_asset = get_account_id(db, "1500")
    ap = get_account_id(db, "2100")
    mfg_clear = get_account_id(db, "2105")
    equity = get_account_id(db, "3000")
    revenue = get_account_id(db, "4000")
    cogs = get_account_id(db, "5000")
    labor = get_account_id(db, "5200")
    rent = get_account_id(db, "6100")
    utilities = get_account_id(db, "6200")
    supplies = get_account_id(db, "6300")

    # 1) Capital injection
    post_journal(db, hq_branch_id, "Owner capital injection", "CAPITAL", [
        {"entry_type": "debit", "accounting_code_id": bank, "amount": Decimal("100000"), "description": "Bank funded"},
        {"entry_type": "credit", "accounting_code_id": equity, "amount": Decimal("100000"), "description": "Owner equity"},
    ])

    # 2) Rent payment
    post_journal(db, hq_branch_id, "Pay rent", "OPEX", [
        {"entry_type": "debit", "accounting_code_id": rent, "amount": Decimal("5000"), "description": "Monthly rent"},
        {"entry_type": "credit", "accounting_code_id": bank, "amount": Decimal("5000"), "description": "Pay rent"},
    ])

    # 3) Utilities payment
    post_journal(db, hq_branch_id, "Pay utilities", "OPEX", [
        {"entry_type": "debit", "accounting_code_id": utilities, "amount": Decimal("1200"), "description": "Utilities"},
        {"entry_type": "credit", "accounting_code_id": bank, "amount": Decimal("1200"), "description": "Pay utilities"},
    ])

    # 4) CapEx fixed asset purchase
    post_journal(db, hq_branch_id, "Purchase equipment", "CAPEX", [
        {"entry_type": "debit", "accounting_code_id": fixed_asset, "amount": Decimal("15000"), "description": "Equipment"},
        {"entry_type": "credit", "accounting_code_id": bank, "amount": Decimal("15000"), "description": "Pay vendor"},
    ])

    # 5) Supplies (non-fixed)
    post_journal(db, hq_branch_id, "Buy supplies", "OPEX", [
        {"entry_type": "debit", "accounting_code_id": supplies, "amount": Decimal("2000"), "description": "Supplies"},
        {"entry_type": "credit", "accounting_code_id": bank, "amount": Decimal("2000"), "description": "Pay vendor"},
    ])

    # 6) Inventory purchase on credit
    post_journal(db, hq_branch_id, "Inventory purchase on credit", "INVENTORY", [
        {"entry_type": "debit", "accounting_code_id": inv_main, "amount": Decimal("10000"), "description": "Buy inventory"},
        {"entry_type": "credit", "accounting_code_id": ap, "amount": Decimal("10000"), "description": "AP"},
    ])

    # 7) Pay supplier (clear AP)
    post_journal(db, hq_branch_id, "Pay supplier", "AP", [
        {"entry_type": "debit", "accounting_code_id": ap, "amount": Decimal("10000"), "description": "Settle AP"},
        {"entry_type": "credit", "accounting_code_id": bank, "amount": Decimal("10000"), "description": "Bank payment"},
    ])

    # 8) Stock allocation to Branch B (transfer)
    post_journal(db, hq_branch_id, "Transfer inventory to Branch B", "INVENTORY", [
        {"entry_type": "debit", "accounting_code_id": inv_b, "amount": Decimal("2000"), "description": "Inventory to Branch B"},
        {"entry_type": "credit", "accounting_code_id": inv_main, "amount": Decimal("2000"), "description": "Inventory from HQ"},
    ])

    # 9) Sale of inventory (with COGS)
    post_journal(db, hq_branch_id, "Sale of inventory", "SALES", [
        {"entry_type": "debit", "accounting_code_id": ar, "amount": Decimal("16000"), "description": "Invoice customer"},
        {"entry_type": "credit", "accounting_code_id": revenue, "amount": Decimal("16000"), "description": "Sales revenue"},
    ])
    post_journal(db, hq_branch_id, "COGS for sale", "SALES", [
        {"entry_type": "debit", "accounting_code_id": cogs, "amount": Decimal("9000"), "description": "COGS"},
        {"entry_type": "credit", "accounting_code_id": inv_main, "amount": Decimal("9000"), "description": "Reduce inventory"},
    ])

    # 10) Simple manufacturing capture (WIP + labor + offset)
    post_journal(db, hq_branch_id, "Manufacturing run", "MFG", [
        {"entry_type": "debit", "accounting_code_id": wip, "amount": Decimal("3000"), "description": "Material+overhead to WIP"},
        {"entry_type": "debit", "accounting_code_id": labor, "amount": Decimal("2000"), "description": "Labor"},
        {"entry_type": "credit", "accounting_code_id": mfg_clear, "amount": Decimal("5000"), "description": "Manufacturing clearing"},
    ])

    db.commit()


def main():
    print("[1/4] Resetting database...")
    reset_database()

    print("[2/4] Seeding dimensions and defaults...")
    seed_dimensions_and_defaults()

    db = SessionLocal()
    try:
        print("[3/4] Seeding minimal chart of accounts and branches...")
        # Ensure branch first so accounts get branch_id
        hq = Branch(name="Head Office", code="HQ", is_head_office=True, currency="USD")
        branch_b = Branch(name="Branch B", code="BRB", is_head_office=False, currency="USD")
        db.add_all([hq, branch_b])
        db.flush()

        seed_chart_of_accounts(db, hq.id)
        db.commit()

        # Apply dimension requirements to accounts
        apply_dimension_requirements(db)
        db.commit()

        # Ensure bank GL exists before creating bank account record
        bank_gl_id = get_account_id(db, "1100")
        bank = BankAccount(name="Operating Bank", account_number="000123456", branch_id=hq.id, accounting_code_id=bank_gl_id)
        db.add(bank)
        db.flush()

        print("[4/4] Running smoke transactions...")
        run_smoke(db, hq.id, branch_b.id)
        print("Smoke test complete. Check journal entries and dimension assignments.")
    finally:
        db.close()


if __name__ == "__main__":
    main()
