import unittest
import os
import sys
import types
from datetime import date, datetime
from decimal import Decimal
from pathlib import Path

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker


ROOT = Path(__file__).resolve().parents[1]
PROJECT_ROOT = ROOT.parent
sys.path.insert(0, str(ROOT))
os.environ.setdefault("DB_NAME", ":memory:.db")
sys.modules.setdefault(
    "app.core.security",
    types.SimpleNamespace(get_current_user=lambda: {"username": "test"}),
)

from app.api.finance import get_finance_summary, get_finance_year_options
from app.core.database import Base
from app.core.models import Customer, Expense, Invoice, InvoiceItem, InvoiceStatus, Payment, PayrollRecord, Tenant


def source(path: str) -> str:
    if path.startswith("frontend/"):
        return (PROJECT_ROOT / path).read_text(encoding="utf-8")
    return (ROOT / path).read_text(encoding="utf-8")


class FinanceReportingTests(unittest.TestCase):
    def test_finance_summary_uses_payment_received_date_for_cash_basis(self) -> None:
        engine = create_engine("sqlite:///:memory:")
        Base.metadata.create_all(engine)
        SessionLocal = sessionmaker(bind=engine)
        db = SessionLocal()
        db.add(Tenant(id=1, company_code="test", name="Test"))
        db.commit()

        customer = Customer(tenant_id=1, name="Cash Basis Customer", company_name="Cash Basis Limited")
        db.add(customer)
        db.flush()

        prior_invoice = Invoice(
            tenant_id=1,
            invoice_number="INV-CASH-001",
            customer_id=customer.id,
            invoice_date=date(2026, 3, 31),
            total_amount=Decimal("1150.00"),
            status=InvoiceStatus.sent,
        )
        period_invoice = Invoice(
            tenant_id=1,
            invoice_number="INV-CASH-002",
            customer_id=customer.id,
            invoice_date=date(2026, 4, 10),
            total_amount=Decimal("230.00"),
            status=InvoiceStatus.sent,
        )
        void_invoice = Invoice(
            tenant_id=1,
            invoice_number="INV-CASH-VOID",
            customer_id=customer.id,
            invoice_date=date(2026, 4, 12),
            total_amount=Decimal("999.00"),
            status=InvoiceStatus.void,
        )
        db.add_all([prior_invoice, period_invoice, void_invoice])
        db.flush()
        db.add(
            InvoiceItem(
                tenant_id=1,
                invoice_id=prior_invoice.id,
                description="Taxable services",
                quantity=Decimal("1"),
                unit_price=Decimal("1000.00"),
                tax_mode="gst_15",
                tax_rate=Decimal("0.1500"),
                subtotal=Decimal("1000.00"),
                tax_amount=Decimal("150.00"),
                total=Decimal("1150.00"),
            )
        )

        db.add(Payment(tenant_id=1, invoice_id=prior_invoice.id, received_date=date(2026, 4, 2), amount=Decimal("575.00")))
        db.add(Payment(tenant_id=1, invoice_id=period_invoice.id, received_date=date(2026, 5, 1), amount=Decimal("230.00")))
        db.add(Payment(tenant_id=1, invoice_id=prior_invoice.id, received_date=None, amount=Decimal("100.00")))
        db.add(
            Payment(
                tenant_id=1,
                invoice_id=prior_invoice.id,
                paid_at=datetime(2026, 4, 8, 9, 15),
                received_date=None,
                amount=Decimal("100.00"),
            )
        )
        db.add(Payment(tenant_id=1, invoice_id=void_invoice.id, received_date=date(2026, 4, 15), amount=Decimal("999.00")))
        db.add(
            Expense(
                tenant_id=1,
                expense_date=date(2026, 4, 4),
                vendor_name="Confirmed Vendor",
                category="Supplies",
                amount_gross=Decimal("80.00"),
                status="confirmed",
            )
        )
        db.add(
            Expense(
                tenant_id=1,
                expense_date=date(2026, 4, 5),
                vendor_name="Draft Vendor",
                category="Supplies",
                amount_gross=Decimal("20.00"),
                status="draft",
            )
        )
        db.commit()

        summary = get_finance_summary(
            current_user=type("U", (), {"username": "tester", "tenant_id": 1})(),
            mode="custom",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
            db=db,
        )

        self.assertEqual(summary.cash_received_total, Decimal("675.00"))
        self.assertEqual(summary.invoice_revenue_total, Decimal("230.00"))
        self.assertEqual(summary.revenue_total, Decimal("675.00"))
        self.assertEqual(summary.confirmed_expense_total, Decimal("80.00"))
        self.assertEqual(summary.expense_total, Decimal("80.00"))
        self.assertEqual(summary.gst_output, Decimal("88.04"))
        self.assertEqual(summary.gst_input, Decimal("10.43"))
        self.assertEqual(summary.gst_payable, Decimal("77.61"))
        self.assertEqual(summary.net_result, Decimal("595.00"))

    def test_finance_year_options_only_include_years_with_real_data(self) -> None:
        engine = create_engine("sqlite:///:memory:")
        Base.metadata.create_all(engine)
        SessionLocal = sessionmaker(bind=engine)
        db = SessionLocal()
        db.add(Tenant(id=1, company_code="test", name="Test"))
        db.commit()

        customer = Customer(tenant_id=1, name="Year Customer", company_name="Year Limited")
        db.add(customer)
        db.flush()
        invoice_2025 = Invoice(
            tenant_id=1,
            invoice_number="INV-YEAR-001",
            customer_id=customer.id,
            invoice_date=date(2025, 7, 1),
            total_amount=Decimal("100.00"),
            status=InvoiceStatus.sent,
        )
        invoice_2026 = Invoice(
            tenant_id=1,
            invoice_number="INV-YEAR-002",
            customer_id=customer.id,
            invoice_date=date(2026, 5, 1),
            total_amount=Decimal("100.00"),
            status=InvoiceStatus.sent,
        )
        db.add_all([invoice_2025, invoice_2026])
        db.flush()
        db.add(Payment(tenant_id=1, invoice_id=invoice_2025.id, received_date=date(2025, 8, 2), amount=Decimal("100.00")))
        db.add(Payment(tenant_id=1, invoice_id=invoice_2026.id, paid_at=datetime(2026, 6, 2, 10, 0), received_date=None, amount=Decimal("100.00")))
        db.add(
            Expense(
                tenant_id=1,
                expense_date=date(2024, 3, 15),
                vendor_name="Old Vendor",
                category="Supplies",
                amount_gross=Decimal("30.00"),
                status="confirmed",
            )
        )
        db.add(
            PayrollRecord(
                tenant_id=1,
                employee_id=1,
                period_start=date(2025, 4, 1),
                period_end=date(2025, 4, 30),
                pay_date=date(2025, 4, 30),
                gross_pay=Decimal("100.00"),
                kiwisaver_employee=Decimal("0.00"),
                kiwisaver_employer=Decimal("0.00"),
                paye_amount=Decimal("0.00"),
                net_pay=Decimal("100.00"),
                status="confirmed",
            )
        )
        db.commit()

        options = get_finance_year_options(current_user=type("U", (), {"username": "tester", "tenant_id": 1})(), db=db)

        self.assertEqual(
            options["calendar_years"],
            [
                {"value": 2026, "label": "2026 自然年"},
                {"value": 2025, "label": "2025 自然年"},
                {"value": 2024, "label": "2024 自然年"},
            ],
        )
        self.assertEqual(
            options["fiscal_years"],
            [
                {"value": 2026, "label": "2026-2027 财年"},
                {"value": 2025, "label": "2025-2026 财年"},
                {"value": 2023, "label": "2023-2024 财年"},
            ],
        )

    def test_finance_api_exposes_summary_and_gst_period_endpoints(self) -> None:
        api = source("app/api/finance.py")
        main = source("app/main.py")

        self.assertIn('prefix="/api/finance"', api)
        self.assertIn('@router.get("/summary"', api)
        self.assertIn('@router.get("/year-options"', api)
        self.assertIn('@router.get("/gst-periods"', api)
        self.assertIn('@router.get("/gst-settings"', api)
        self.assertIn('@router.put("/gst-settings"', api)
        self.assertIn('@router.get("/gst-return/draft"', api)
        self.assertIn('@router.post("/gst-return/prepare"', api)
        self.assertIn('@router.post("/gst-return/{return_id}/lock"', api)
        self.assertIn('@router.post("/gst-return/{return_id}/file"', api)
        self.assertIn('@router.post("/gst-return/adjustments"', api)
        self.assertIn('@router.delete("/gst-return/adjustments/{adjustment_id}"', api)
        self.assertIn('@router.get("/gst-returns"', api)
        self.assertIn('@router.get("/gst-return/{return_id}"', api)
        self.assertIn("build_gst_return_draft", api)
        self.assertIn("create_gst_adjustment", api)
        self.assertIn("prepare_gst_return", api)
        self.assertIn("lock_gst_return", api)
        self.assertIn("file_gst_return", api)
        self.assertIn("cycle_months", api)
        self.assertIn("issued", api)
        self.assertIn("confirmed", api)
        self.assertIn("Payment.received_date", api)
        self.assertIn("Payment.paid_at", api)
        self.assertIn("func.coalesce", api)
        self.assertIn("cash_received_total", api)
        self.assertIn("invoice_revenue_total", api)
        self.assertIn("confirmed_expense_total", api)
        self.assertIn("gst_output", api)
        self.assertIn("gst_input", api)
        self.assertIn("gst_payable", api)
        self.assertIn("Payment.received_date", source("app/services/gst_service.py"))
        self.assertIn("Payment.paid_at", source("app/services/gst_service.py"))
        self.assertIn('Expense.status == "confirmed"', source("app/services/gst_service.py"))
        self.assertIn("app.include_router(finance_router)", main)

    def test_finance_frontend_exposes_gst_return_draft_wrapper(self) -> None:
        api = source("frontend/src/api/finance.js")

        self.assertIn("gstReturnDraft", api)
        self.assertIn("prepareGstReturn", api)
        self.assertIn("saveGstDraft", api)
        self.assertIn("deleteGstReturn", api)
        self.assertIn("lockGstReturn", api)
        self.assertIn("fileGstReturn", api)
        self.assertIn("getGstReturn", api)
        self.assertIn("createGstAdjustment", api)
        self.assertIn("deleteGstAdjustment", api)
        self.assertIn("listGstReturns", api)
        self.assertIn("yearOptions", api)
        self.assertIn("/finance/gst-return/draft", api)
        self.assertIn("/finance/gst-return/prepare", api)
        self.assertIn("/finance/gst-return/drafts", api)
        self.assertIn("/finance/gst-return/${returnId}", api)
        self.assertIn("/finance/gst-return/adjustments", api)
        self.assertIn("/finance/gst-return/adjustments/${adjustmentId}", api)
        self.assertIn("/finance/gst-returns", api)
        self.assertIn("/finance/year-options", api)
        self.assertIn("/finance/gst-return/${returnId}/lock", api)
        self.assertIn("/finance/gst-return/${returnId}/file", api)
        self.assertIn("period_start", api)
        self.assertIn("period_end", api)

    def test_finance_frontend_exposes_gst_return_page(self) -> None:
        page_path = PROJECT_ROOT / "frontend/src/pages/GstReturnPage.vue"
        self.assertTrue(page_path.exists())

        page = source("frontend/src/pages/GstReturnPage.vue")

        self.assertIn("gstReturnDraft", page)
        self.assertIn("prepareGstReturn", page)
        self.assertIn("saveGstDraft", page)
        self.assertIn("deleteGstReturn", source("frontend/src/pages/GstReturnsListPage.vue"))
        self.assertIn("lockGstReturn", page)
        self.assertIn("fileGstReturn", page)
        self.assertIn("createGstAdjustment", page)
        self.assertIn("deleteGstAdjustment", page)
        self.assertIn("deleteAdjustment", page)
        self.assertIn("payment_rows", page)
        self.assertIn("expense_rows", page)
        self.assertIn("adjustment_rows", page)
        self.assertIn("adjustmentForm", page)
        self.assertIn("adjustment_type", page)
        self.assertIn("source_period_start", page)
        self.assertIn("source_period_end", page)
        self.assertIn("target_period_start", page)
        self.assertIn("target_period_end", page)
        self.assertIn("linked_invoice_id", page)
        self.assertIn("linked_payment_id", page)
        self.assertIn("linked_expense_id", page)
        self.assertIn("validateAdjustmentForm", page)
        self.assertIn("reason.trim()", page)
        self.assertIn("required", page)
        self.assertIn("min=\"0.01\"", page)
        self.assertIn("提交调整", page)
        self.assertIn("<option value=\"debit\">Debit 调增</option>", page)
        self.assertIn("<option value=\"credit\">Credit 调减</option>", page)
        self.assertIn("准备快照", page)
        self.assertIn("保存草稿", page)
        self.assertIn("生成申报记录", page)
        self.assertIn("锁定", page)
        self.assertIn("标记已申报", page)
        self.assertIn("GST 申报", page)
        self.assertIn("收款明细", page)
        self.assertIn("支出明细", page)
        self.assertIn("调整明细", page)
        self.assertIn("删除调整", page)

    def test_finance_frontend_routes_and_links_gst_return_pages(self) -> None:
        router = source("frontend/src/router/index.js")
        summary_page = source("frontend/src/pages/FinanceSummaryPage.vue")
        list_page = PROJECT_ROOT / "frontend/src/pages/GstReturnsListPage.vue"
        layout = source("frontend/src/components/AppLayout.vue")

        self.assertIn("finance/gst-return", router)
        self.assertIn("finance/gst-returns", router)
        self.assertIn("GstReturn", router)
        self.assertIn("GstReturnsList", router)
        self.assertIn("GstReturnPage.vue", router)
        self.assertIn("GstReturnsListPage.vue", router)
        self.assertIn("/finance/gst-return", summary_page)
        self.assertIn("/finance/gst-returns", summary_page)
        self.assertTrue(list_page.exists())
        list_source = source("frontend/src/pages/GstReturnsListPage.vue")
        self.assertIn("listGstReturns", list_source)
        self.assertIn("删除", list_source)
        self.assertIn("router.back", layout)
        self.assertIn("返回", layout)
        self.assertIn("GST申报", layout)

    def test_finance_summary_page_labels_cash_basis_and_invoice_reference(self) -> None:
        summary_page = source("frontend/src/pages/FinanceSummaryPage.vue")

        self.assertIn("现金收款", summary_page)
        self.assertIn("Cash received", summary_page)
        self.assertIn("开票收入", summary_page)
        self.assertIn("Reference", summary_page)
        self.assertIn("已确认支出", summary_page)
        self.assertIn("销项 GST", summary_page)
        self.assertIn("进项 GST", summary_page)
        self.assertIn("应付/应退 GST", summary_page)
        self.assertIn("按真实收款日统计", summary_page)
        self.assertIn("selectedFiscalYear", summary_page)
        self.assertIn("selectedCalendarYear", summary_page)
        self.assertIn("yearOptions", summary_page)
        self.assertIn("loadYearOptions", summary_page)
        self.assertIn("自然年", summary_page)
        self.assertIn("/finance/gst-return", summary_page)
        self.assertNotIn("GST totals are based on invoice date", summary_page)


if __name__ == "__main__":
    unittest.main()
