from __future__ import annotations

import unittest
from datetime import UTC, datetime
from decimal import Decimal
from io import BytesIO

from openpyxl import Workbook, load_workbook
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 import master_data, sales  # noqa: E402
from app.models.base import Base  # noqa: E402
from app.models.master_data import Item, Material  # noqa: E402
from app.models.sales import Customer  # noqa: E402


def _workbook_bytes(headers: list[str], rows: list[list[object]]) -> bytes:
    workbook = Workbook()
    worksheet = workbook.active
    worksheet.append(headers)
    for row in rows:
        worksheet.append(row)
    output = BytesIO()
    workbook.save(output)
    return output.getvalue()


class MaterialCustomerExcelImportExportTest(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()

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

    def test_material_import_upserts_by_code_and_exports_performance_requirement(self) -> None:
        now = datetime.now(UTC)
        existing = Item(
            item_code="原材料00001",
            item_name="旧冷轧钢板",
            item_type="RAW_MATERIAL",
            specification="旧规格",
            material_grade="旧材质",
            unit_weight_kg=Decimal("1"),
            safety_stock_weight_kg=Decimal("0"),
            scrap_sale_price=Decimal("0"),
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        self.db.add(existing)
        self.db.flush()
        self.db.add(
            Material(
                item_id=existing.id,
                default_supplier_id=None,
                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,
                quality_rule="旧性能要求",
            )
        )
        self.db.commit()

        result = master_data._import_material_excel(
            self.db,
            _workbook_bytes(
                master_data.MATERIAL_EXCEL_HEADERS,
                [
                    ["原材料00001", "冷轧钢板", "SPCC", "1.0*1000", 2.5, "表面无划伤", "启用"],
                    ["", "铝合金", "6061", "2.0*500", 4.2, "阳极氧化", "启用"],
                ],
            ),
        )

        self.assertEqual(result["updated"], 1)
        self.assertEqual(result["imported"], 1)
        updated_item = self.db.scalar(select(Item).where(Item.item_code == "原材料00001"))
        self.assertEqual(updated_item.item_name, "冷轧钢板")
        self.assertEqual(updated_item.material_grade, "SPCC")
        self.assertEqual(float(updated_item.scrap_sale_price), 2.5)
        updated_material = self.db.scalar(select(Material).where(Material.item_id == updated_item.id))
        self.assertEqual(updated_material.quality_rule, "表面无划伤")

        workbook = master_data._build_material_workbook(
            self.db.execute(master_data._material_export_stmt()).mappings().all()
        )
        exported = BytesIO()
        workbook.save(exported)
        rows = list(load_workbook(BytesIO(exported.getvalue()), read_only=True, data_only=True).active.iter_rows(values_only=True))
        self.assertEqual(list(rows[0]), master_data.MATERIAL_EXCEL_HEADERS)
        self.assertIn(("原材料00001", "冷轧钢板", "SPCC", "1.0*1000", 2.5, "表面无划伤", "启用"), rows[1:])

    def test_customer_import_upserts_by_name_and_exports_three_contacts(self) -> None:
        now = datetime.now(UTC)
        self.db.add(
            Customer(
                customer_code="CUS-EXCEL-001",
                customer_name="老客户",
                short_name="老客户",
                contact_name="旧联系人",
                contact_phone="13000000000",
                address="旧地址",
                credit_days=15,
                status="ACTIVE",
                created_at=now,
                updated_at=now,
            )
        )
        self.db.commit()

        result = sales._import_customer_excel(
            self.db,
            _workbook_bytes(
                sales.CUSTOMER_EXCEL_HEADERS,
                [
                    ["老客户", "老客", "张三", "13800000001", "李四", "13800000002", "王五", "13800000003", "新地址", 30],
                    ["新客户", "新客", "赵六", "13900000001", "", "", "", "", "新客户地址", 45],
                ],
            ),
        )

        self.assertEqual(result["updated"], 1)
        self.assertEqual(result["imported"], 1)
        updated = self.db.scalar(select(Customer).where(Customer.customer_name == "老客户"))
        self.assertEqual(updated.short_name, "老客")
        self.assertEqual(updated.contact_name, "张三、李四、王五")
        self.assertEqual(updated.contact_phone, "13800000001、13800000002、13800000003")
        self.assertEqual(updated.address, "新地址")
        self.assertEqual(updated.credit_days, 30)

        workbook = sales._build_customer_workbook(self.db.scalars(select(Customer).order_by(Customer.customer_name.asc())).all())
        exported = BytesIO()
        workbook.save(exported)
        rows = list(load_workbook(BytesIO(exported.getvalue()), read_only=True, data_only=True).active.iter_rows(values_only=True))
        self.assertEqual(list(rows[0]), sales.CUSTOMER_EXCEL_HEADERS)
        self.assertIn(("老客户", "老客", "张三", "13800000001", "李四", "13800000002", "王五", "13800000003", "新地址", 30), rows[1:])


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