from __future__ import annotations

import unittest
from datetime import date, timedelta
from decimal import Decimal

from fastapi import HTTPException
from sqlalchemy import BigInteger, create_engine, select
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.orm import Session, sessionmaker


@compiles(BigInteger, "sqlite")
def _compile_bigint_for_sqlite(type_, compiler, **kw) -> str:
    _ = type_, compiler, kw
    return "INTEGER"


import app.models.master_data  # noqa: E402,F401
import app.models.miniapp  # noqa: E402,F401
import app.models.operations  # noqa: E402,F401
import app.models.org  # noqa: E402,F401
import app.models.planning  # noqa: E402,F401
import app.models.sales  # noqa: E402,F401
from app.api.routes.purchase import create_purchase_order, delete_purchase_order, lock_purchase_order, update_purchase_order  # noqa: E402
from app.api.routes.sales import delete_sales_order  # noqa: E402
from app.models.base import Base  # noqa: E402
from app.models.master_data import Bom, BomItem, Item, Material  # noqa: E402
from app.models.operations import PurchaseOrder, PurchaseOrderItem, PurchaseOrderSalesOrderLink, Supplier  # noqa: E402
from app.models.planning import MaterialDemand  # noqa: E402
from app.models.sales import Customer, SalesOrder, SalesOrderItem  # noqa: E402
from app.schemas.operations import PurchaseOrderCreate, PurchaseOrderItemCreate, PurchaseOrderUpdate  # noqa: E402
from app.services.operations import get_purchase_order_query, sync_purchase_order_item_status, sync_purchase_order_status  # noqa: E402


class SalesOrderPurchaseLockFlowTest(unittest.TestCase):
    def setUp(self) -> None:
        engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
        Base.metadata.create_all(engine)
        self.SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
        self.db: Session = self.SessionLocal()
        self._seed_master_data()

    def tearDown(self) -> None:
        self.db.close()

    def _seed_master_data(self) -> None:
        self.customer = Customer(
            id=1,
            customer_code="CUS-LOCK-001",
            customer_name="采购关联客户",
            short_name="采购客户",
            status="ACTIVE",
        )
        self.product = Item(
            id=1,
            item_code="FG-LOCK-001",
            item_name="锁单测试成品",
            item_type="FINISHED_GOOD",
            unit_weight_kg=Decimal("1"),
            status="ACTIVE",
        )
        self.material = Item(
            id=2,
            item_code="RM-LOCK-001",
            item_name="锁单测试原料",
            item_type="RAW_MATERIAL",
            unit_weight_kg=Decimal("1"),
            status="ACTIVE",
        )
        self.supplier = Supplier(
            id=1,
            supplier_code="SUP-LOCK-001",
            supplier_name="锁单供应商",
            short_name="锁单供应商",
            lead_time_days=0,
            default_tax_rate=Decimal("0"),
            status="ACTIVE",
        )
        self.db.add_all([self.customer, self.product, self.material, self.supplier])
        self.db.flush()
        self.db.add(
            Material(
                item_id=self.material.id,
                default_supplier_id=self.supplier.id,
                purchase_calc_mode="BY_WEIGHT",
                min_purchase_qty=Decimal("0"),
                purchase_multiple_qty=Decimal("0"),
                purchase_multiple_weight_kg=Decimal("0"),
                lead_time_days=0,
            )
        )
        self.bom = Bom(
            id=1,
            bom_code="BOM-LOCK-001",
            product_item_id=self.product.id,
            version_no="V1.0",
            is_default=1,
            status="ACTIVE",
        )
        self.bom_item = BomItem(
            id=1,
            bom_id=1,
            seq_no=1,
            material_item_id=self.material.id,
            usage_type="WEIGHT",
            gross_weight_per_piece_kg=Decimal("1"),
            loss_rate=Decimal("0"),
        )
        self.db.add_all([self.bom, self.bom_item])
        self.sales_order = SalesOrder(
            id=1,
            order_no="SO-LOCK-001",
            customer_id=self.customer.id,
            order_date=date.today(),
            status="OPEN",
        )
        self.sales_order_item = SalesOrderItem(
            id=1,
            sales_order_id=self.sales_order.id,
            line_no=1,
            product_item_id=self.product.id,
            order_qty=Decimal("100"),
            delivered_qty=Decimal("0"),
            unit_price=Decimal("10"),
            line_amount=Decimal("1000"),
            status="OPEN",
        )
        self.demand = MaterialDemand(
            id=1,
            demand_no="MRP-LOCK-001",
            sales_order_item_id=self.sales_order_item.id,
            product_item_id=self.product.id,
            material_item_id=self.material.id,
            bom_item_id=self.bom_item.id,
            order_qty=Decimal("100"),
            theoretical_weight_kg=Decimal("100"),
            planned_weight_kg=Decimal("100"),
            available_stock_weight_kg=Decimal("0"),
            shortage_weight_kg=Decimal("100"),
            suggested_purchase_qty=Decimal("100"),
            suggested_purchase_weight_kg=Decimal("100"),
            status="NEW",
        )
        self.db.add_all([self.sales_order, self.sales_order_item, self.demand])
        self.db.commit()

    def test_purchase_order_tracks_sales_order_source_and_can_be_locked(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        source_demand_id=self.demand.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )

        self.assertEqual(result.source_type, "SALES_ORDER")
        self.assertEqual(result.source_order_nos, "SO-LOCK-001")

        locked = lock_purchase_order(result.purchase_order_id, db=self.db)
        self.assertEqual(locked.status, "LOCKED")

        with self.assertRaises(HTTPException) as exc:
            delete_sales_order(self.sales_order.id, db=self.db)
        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("已关联采购订单", exc.exception.detail)

    def test_purchase_order_warning_lead_days_marks_unreceived_order(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today() + timedelta(days=3),
                warning_lead_days=3,
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )

        self.assertEqual(result.warning_lead_days, 3)
        self.assertEqual(result.warning_start_date, date.today())
        self.assertTrue(result.is_warning_active)

    def test_purchase_order_warning_is_inactive_before_warning_start(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today() + timedelta(days=4),
                warning_lead_days=3,
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )

        self.assertEqual(result.warning_lead_days, 3)
        self.assertEqual(result.warning_start_date, date.today() + timedelta(days=1))
        self.assertFalse(result.is_warning_active)

    def test_explicit_purchase_order_link_blocks_sales_order_delete(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                sales_order_ids=[self.sales_order.id],
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )
        self.assertIsNotNone(
            self.db.scalar(
                select(PurchaseOrderSalesOrderLink).where(
                    PurchaseOrderSalesOrderLink.purchase_order_id == result.purchase_order_id,
                    PurchaseOrderSalesOrderLink.sales_order_id == self.sales_order.id,
                )
            )
        )

        with self.assertRaises(HTTPException) as exc:
            delete_sales_order(self.sales_order.id, db=self.db)

        self.assertEqual(exc.exception.status_code, 400)
        self.assertEqual(exc.exception.detail, "销售订单已关联采购订单，不能删除")

    def test_purchase_order_query_does_not_use_group_concat(self) -> None:
        compiled = str(get_purchase_order_query().compile(self.db.get_bind()))

        self.assertNotIn("group_concat", compiled.lower())

    def test_open_purchase_order_can_be_updated_before_lock(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        source_demand_id=self.demand.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )

        updated = update_purchase_order(
            result.purchase_order_id,
            PurchaseOrderUpdate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                remark="修改未锁单采购订单",
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        source_demand_id=self.demand.id,
                        order_weight_kg=Decimal("120"),
                        unit_price=Decimal("6"),
                    )
                ],
            ),
            db=self.db,
        )

        self.assertEqual(updated.po_no, result.po_no)
        self.assertEqual(updated.status, "OPEN")
        self.assertEqual(float(updated.total_order_weight_kg), 120)
        self.assertEqual(float(updated.total_amount), 720)
        item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == result.purchase_order_id))
        self.assertEqual(float(item.order_weight_kg), 120)
        self.assertEqual(float(item.unit_price), 6)
        self.assertEqual(self.db.get(MaterialDemand, self.demand.id).status, "ORDERED")

    def test_locked_purchase_order_cannot_be_updated_or_deleted(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        source_demand_id=self.demand.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )
        lock_purchase_order(result.purchase_order_id, db=self.db)

        with self.assertRaises(HTTPException) as update_exc:
            update_purchase_order(
                result.purchase_order_id,
                PurchaseOrderUpdate(
                    supplier_id=self.supplier.id,
                    expected_date=date.today(),
                    items=[
                        PurchaseOrderItemCreate(
                            material_item_id=self.material.id,
                            source_demand_id=self.demand.id,
                            order_weight_kg=Decimal("110"),
                            unit_price=Decimal("5"),
                        )
                    ],
                ),
                db=self.db,
            )
        self.assertEqual(update_exc.exception.status_code, 400)

        with self.assertRaises(HTTPException) as delete_exc:
            delete_purchase_order(result.purchase_order_id, db=self.db)
        self.assertEqual(delete_exc.exception.status_code, 400)

    def test_open_purchase_order_can_be_deleted_and_resets_unreferenced_demand(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                items=[
                    PurchaseOrderItemCreate(
                        material_item_id=self.material.id,
                        source_demand_id=self.demand.id,
                        order_weight_kg=Decimal("100"),
                        unit_price=Decimal("5"),
                    )
                ],
            ),
            db=self.db,
        )
        self.assertEqual(self.db.get(MaterialDemand, self.demand.id).status, "ORDERED")

        delete_result = delete_purchase_order(result.purchase_order_id, db=self.db)

        self.assertIn("已删除", delete_result["message"])
        self.assertIsNone(self.db.get(PurchaseOrder, result.purchase_order_id))
        self.assertIsNone(self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == result.purchase_order_id)))
        self.assertEqual(self.db.get(MaterialDemand, self.demand.id).status, "NEW")

    def test_unlinked_sales_order_can_be_deleted(self) -> None:
        order = SalesOrder(
            id=2,
            order_no="SO-DELETE-001",
            customer_id=self.customer.id,
            order_date=date.today(),
            status="OPEN",
        )
        item = SalesOrderItem(
            id=2,
            sales_order_id=order.id,
            line_no=1,
            product_item_id=self.product.id,
            order_qty=Decimal("1"),
            delivered_qty=Decimal("0"),
            unit_price=Decimal("1"),
            line_amount=Decimal("1"),
            status="OPEN",
        )
        self.db.add_all([order, item])
        self.db.commit()
        order_id = order.id
        item_id = item.id

        result = delete_sales_order(order_id, db=self.db)

        self.assertIn("已删除", result["message"])
        self.assertIsNone(self.db.get(SalesOrder, order_id))
        self.assertIsNone(self.db.get(SalesOrderItem, item_id))

    def test_locked_purchase_order_stays_locked_after_partial_receipt_status_sync(self) -> None:
        order = PurchaseOrder(
            id=10,
            po_no="PO-LOCK-001",
            supplier_id=self.supplier.id,
            order_date=date.today(),
            status="LOCKED",
        )
        item = PurchaseOrderItem(
            id=10,
            purchase_order_id=order.id,
            line_no=1,
            material_item_id=self.material.id,
            order_qty=Decimal("100"),
            order_weight_kg=Decimal("100"),
            received_qty=Decimal("50"),
            received_weight_kg=Decimal("50"),
            unit_price=Decimal("5"),
            line_amount=Decimal("500"),
            status="OPEN",
        )
        self.db.add_all([order, item])
        self.db.commit()

        sync_purchase_order_item_status(item)
        self.db.add(item)
        sync_purchase_order_status(self.db, order.id)
        self.db.commit()

        self.assertEqual(item.status, "PARTIAL")
        self.assertEqual(self.db.get(PurchaseOrder, order.id).status, "LOCKED")


if __name__ == "__main__":
    unittest.main()
