"""Dựng lại dự toán Cống hộp Ông Đèn – Bà Lanh từ hồ sơ THẨM ĐỊNH và đối chiếu Phụ lục 01 hợp đồng.

Tại sao có công cụ này
----------------------
Dự toán ERP tự sinh trước đây (5.825.878.696 đ) không dùng được: tự đặt đơn giá, thay mã định mức
bằng "TT.xx", xếp sai nhóm. Công cụ này KHÔNG tính lại gì theo định mức. Nó chỉ:
  1. Đọc nguyên văn bảng dự toán thẩm định (sheet "Công trình" + "THKP hạng mục"),
  2. Tự kiểm tra: tổng thành tiền từng dòng phải khớp dòng A1/B1/C1 của THKP,
  3. Ghép từng dòng với Phụ lục 01 hợp đồng (cùng thứ tự, cùng mã) để thấy chênh khối lượng/đơn giá,
  4. Lượng hoá các rủi ro H1–H6 của báo cáo audit, chỉ dùng đơn giá có sẵn trong TĐ.

Nguồn (Luật 1 – không dữ liệu ảo)
---------------------------------
- TĐ : ...\\Cống hộp ông Đèn đến nhà bà Lanh\\thẩm định cống bà Lanh_Dũng\\02. DToan thanh dinh ba Lanh.xls
       (chuyển .xls -> .xlsx bằng Excel COM, không sửa nội dung) -> reports/ong_den/thamdinh_ba_lanh.xlsx
- HĐ : ...\\Gói TCXL\\phu luc 01&02.xlsx -> reports/ong_den/phu_luc_01_02_HD.xlsx

Chạy:  python -m tools.estimation.ong_den_rebuild_estimate [--td ...] [--hd ...] [--out ...]
"""
from __future__ import annotations

import argparse
from dataclasses import dataclass, field
from decimal import Decimal, InvalidOperation
from pathlib import Path

import openpyxl
from openpyxl.styles import Alignment, Font, PatternFill

ROOT = Path(__file__).resolve().parents[2]
DEFAULT_TD = ROOT / "reports" / "ong_den" / "thamdinh_ba_lanh.xlsx"
DEFAULT_HD = ROOT / "reports" / "ong_den" / "phu_luc_01_02_HD.xlsx"
DEFAULT_OUT = ROOT / "reports" / "ong_den" / "Du_toan_Ong_Den_dung_lai_tu_TD.xlsx"
CONTRACT_VALUE = Decimal("6482295000")  # Phụ lục 01, dòng "LÀM TRÒN"

# Cột (0-based) của sheet "Công trình" (phần mềm dự toán GXD) – xác định từ dòng tiêu đề 4–5 của file TĐ
C_STT, C_CODE, C_NAME, C_UNIT, C_QTY_EXPLAIN, C_QTY = 0, 4, 5, 6, 14, 15
C_DG = {"vl": 16, "vlp": 17, "nc": 18, "m": 19}
C_TT = {"vl": 20, "vlp": 21, "nc": 22, "m": 23}

# Mã công tác cấu thành khối lượng chủ yếu của thân cống (sheet "Công trình", mục II – Phân đoạn 1+2+3)
M250_CODES = ("AF.11213", "AF.12113", "AF.22313")  # BT móng 133,867 + tường 197,019 + nắp 108,75 m3
REBAR_CODES = ("AF.61120", "AF.61321", "AF.61721")  # thép móng 15,395 + tường 26,848 + nắp 15,395 t


def D(v) -> Decimal:
    """Ô Excel -> Decimal (độ chính xác tài chính, không đi qua float khi cộng dồn)."""
    if v in (None, ""):
        return Decimal("0")
    try:
        return Decimal(str(v).strip().replace(",", ""))
    except InvalidOperation:
        return Decimal("0")


@dataclass
class Item:
    stt: str
    code: str
    name: str
    unit: str
    qty: Decimal
    dg: dict[str, Decimal]
    tt: dict[str, Decimal]
    section: str
    explain: list[str] = field(default_factory=list)

    @property
    def unit_direct(self) -> Decimal:
        """Đơn giá trực tiếp (VL + VL phụ + NC + M) theo TĐ."""
        return sum(self.dg.values(), Decimal("0"))

    @property
    def direct_total(self) -> Decimal:
        return sum(self.tt.values(), Decimal("0"))


@dataclass
class Package:
    name: str
    items: list[Item] = field(default_factory=list)
    thkp_rows: list[tuple] = field(default_factory=list)  # (STT, nội dung, ký hiệu, cách tính, hệ số, giá trị)
    thkp: dict[str, Decimal] = field(default_factory=dict)  # ký hiệu -> giá trị (lần xuất hiện đầu)
    rounded_total: Decimal = Decimal("0")

    def sum_items(self, kind: str) -> Decimal:
        if kind == "vl":  # THKP gộp VL chính + VL phụ vào "A1 – Theo đơn giá trực tiếp"
            return sum((i.tt["vl"] + i.tt["vlp"] for i in self.items), Decimal("0"))
        return sum((i.tt[kind] for i in self.items), Decimal("0"))


@dataclass
class Estimate:
    packages: list[Package]
    source: Path

    @property
    def grand_total(self) -> Decimal:
        return sum((p.rounded_total for p in self.packages), Decimal("0"))

    def all_items(self) -> list[tuple[int, Item]]:
        return [(k, it) for k, p in enumerate(self.packages) for it in p.items]

    def key_quantities(self) -> dict[str, float]:
        items = self.packages[0].items
        return {
            "be_tong_m250_m3": float(sum((i.qty for i in items if i.code in M250_CODES), Decimal("0"))),
            "cot_thep_tan": float(sum((i.qty for i in items if i.code in REBAR_CODES), Decimal("0"))),
        }

    def contract_factor(self, contract_value: Decimal) -> float:
        """Hệ số giá HĐ / giá TĐ (thể hiện mức giảm giá khi ký hợp đồng)."""
        return float(contract_value / self.grand_total)

    def find(self, code: str, name_part: str = "") -> Item | None:
        for _, it in self.all_items():
            if it.code == code and name_part.lower() in it.name.lower():
                return it
        return None


def load_appraised_estimate(path: Path) -> Estimate:
    wb = openpyxl.load_workbook(path, data_only=True, read_only=True)

    # ---- 1. Bảng dự toán chi tiết ----
    packages: list[Package] = []
    section = ""
    last: Item | None = None
    for r in wb["Công trình"].iter_rows(min_row=6, values_only=True):
        r = list(r) + [None] * 40
        stt, code, name = r[C_STT], r[C_CODE], str(r[C_NAME] or "").strip()
        if code == "HM":
            packages.append(Package(name=name))
            section, last = "", None
        elif not packages:
            continue
        elif stt not in (None, "") and code not in (None, ""):
            last = Item(
                stt=str(stt), code=str(code).strip(), name=name, unit=str(r[C_UNIT] or "").strip(),
                qty=D(r[C_QTY]), dg={k: D(r[c]) for k, c in C_DG.items()}, tt={k: D(r[c]) for k, c in C_TT.items()},
                section=section,
            )
            packages[-1].items.append(last)
        elif name and r[C_QTY_EXPLAIN] not in (None, "") and last is not None:
            last.explain.append(name)  # dòng diễn giải khối lượng của công tác ngay trên
        elif name and r[C_QTY] in (None, "") and r[C_QTY_EXPLAIN] in (None, ""):
            section, last = name, None  # tiêu đề mục (I., II., 2.1 ...)

    # ---- 2. Tổng hợp kinh phí hạng mục (mỗi hạng mục 1 khối, mở đầu bằng "BẢNG TỔNG HỢP") ----
    k = -1
    for r in wb["THKP hạng mục"].iter_rows(values_only=True):
        r = list(r) + [None] * 10
        title = str(r[0] or "")
        if title.upper().startswith("BẢNG TỔNG HỢP"):
            k += 1
            continue
        if k < 0 or k >= len(packages) or r[7] in (None, "") or r[1] in (None, ""):
            continue
        content, sym, val = str(r[1]).strip(), str(r[2] or "").strip(), D(r[7])
        packages[k].thkp_rows.append((r[0], content, sym, r[3], r[6], val))
        if sym and sym not in packages[k].thkp:
            packages[k].thkp[sym] = val
        if content.upper() == "LÀM TRÒN":
            packages[k].rounded_total = val
    return Estimate(packages=packages, source=Path(path))


def load_contract_annex(path: Path) -> list[dict]:
    """Phụ lục 01: STT | Mã số | Tên công tác | Đơn vị | KL | VL | NC | M | Đơn giá | Thành tiền."""
    ws = openpyxl.load_workbook(path, data_only=True, read_only=True)["phụ lục 01"]
    rows, part = [], ""
    for r in ws.iter_rows(min_row=4, values_only=True):
        r = list(r) + [None] * 12
        if r[1] in ("A", "B"):
            part = r[1]
        if r[1] in (None, "") or r[2] in (None, "", 0, "0") or not str(r[1]).isdigit():
            continue
        rows.append({"part": part, "stt": str(r[1]), "code": str(r[2]).strip(), "name": str(r[3] or ""),
                     "unit": str(r[4] or ""), "qty": D(r[5]), "unit_price": D(r[9]), "amount": D(r[10])})
    return rows


def align_with_contract(est: Estimate, hd_rows: list[dict]) -> list[tuple[int, tuple[Item | None, dict | None]]]:
    """Ghép dòng TĐ ↔ dòng Phụ lục 01 theo CHUỖI MÃ định mức trong từng hạng mục (A ↔ HM1, B ↔ HM2).

    Không ghép theo vị trí tuyệt đối vì HĐ đã gộp/bỏ một số dòng (vd. "vận chuyển 10m tiếp theo"),
    làm lệch toàn bộ phần sau. difflib.SequenceMatcher tìm đoạn mã trùng dài nhất -> ghép 1-1 ổn định.
    """
    import difflib

    out: list[tuple[int, tuple[Item | None, dict | None]]] = []
    for k, pkg in enumerate(est.packages):
        part = "A" if k == 0 else "B"
        hd = [h for h in hd_rows if h["part"] == part]
        td_codes, hd_codes = [i.code for i in pkg.items], [h["code"] for h in hd]
        sm = difflib.SequenceMatcher(None, td_codes, hd_codes, autojunk=False)
        for op, a0, a1, b0, b1 in sm.get_opcodes():
            if op == "equal":
                out += [(k, (pkg.items[a0 + j], hd[b0 + j])) for j in range(a1 - a0)]
            else:  # replace/delete/insert: tách riêng để người đọc thấy rõ khác biệt
                out += [(k, (pkg.items[a], None)) for a in range(a0, a1)]
                out += [(k, (None, hd[b])) for b in range(b0, b1)]
    return out


def risk_rows(est: Estimate) -> list[list]:
    """H1–H6 của báo cáo audit. Chỉ dùng đơn giá TĐ; mọi giả định ghi rõ trong cột 'Cơ sở / giả định'."""
    rent = est.find("TT", "Cừ thép thuê")
    drive, pull = est.find("AC.22111"), est.find("AC.23420")
    pump = est.find("TT", "Bơm nước")
    out = []

    # H1 – thuê cừ: TĐ tính 90 ngày cho 1/3 tổng chiều dài (luân chuyển 3 lần)
    rent_price = rent.dg["vl"] if rent else Decimal("2000")
    m_in_ground = Decimal("7030") / 3
    days_gantt = Decimal("230")
    td_cost = rent.direct_total if rent else m_in_ground * 90 * rent_price
    gantt_cost = m_in_ground * days_gantt * rent_price
    out.append(["H1", "high", "Thuê cừ chỉ tính 90 ngày; theo Gantt HĐ cừ nằm trong đất ≈ 230 ngày (ép PĐ1 → rút 30/05/2027)",
                f"7.030/3 = {m_in_ground:.1f} m × {rent_price} đ/m/ngày; TĐ {rent.qty if rent else '?'} m·ngày; Gantt: ≈230 ngày (ước tính từ Gantt, cần chốt lại)",
                float(td_cost), float(gantt_cost), float(gantt_cost - td_cost)])

    # H2 – cừ 8 m đoạn TC1–C4 (BPTC) trong khi KL TĐ nhân 5 m
    extra_100m = Decimal("6.74")  # 28,07 m × 2 hàng × 4 thanh/m × 3 m ≈ 674 m (audit, từ lý trình DWG)
    up = (drive.unit_direct if drive else 0) + (pull.unit_direct if pull else 0)
    out.append(["H2", "medium", "Đoạn TC1–C4 dùng cừ 8 m (BPTC) nhưng KL TĐ tính 5 m",
                "≈ 674 m cừ đóng + nhổ thêm × đơn giá trực tiếp TĐ (AC.22111 + AC.23420)",
                0.0, float(extra_100m * up), float(extra_100m * up)])

    # H3 – bơm nước 50 ca vs BPTC 2 máy chạy liên tục
    pump_up = pump.unit_direct if pump else Decimal("0")
    ca_bptc = Decimal("2") * Decimal("215")  # giả định: 2 máy × 215 ngày đào/đổ × 1 ca/ngày (kịch bản thấp)
    out.append(["H3", "medium", "Bơm nước chỉ 50 ca; BPTC cam kết 2 máy bơm 24/24 suốt giai đoạn đào/đổ",
                f"Kịch bản thấp: 2 máy × 215 ngày × 1 ca = {ca_bptc} ca × {pump_up:,.0f} đ/ca (đơn giá TĐ)",
                float(pump.direct_total if pump else 0), float(ca_bptc * pump_up),
                float(ca_bptc * pump_up - (pump.direct_total if pump else 0))])

    out.append(["H4", "medium", "Gantt xếp TC1–C4 vào PĐ2, nhưng đê quai PĐ2 đặt ở Km0+97,98 → TC1–C4 thuộc PĐ3",
                "Mặt bằng DWG (đê quai Km0+50,48 & Km0+97,98) – cần sửa Gantt/BPTC", None, None, None])
    out.append(["H5", "medium", "Gantt chỉ có 1 đợt rút cừ 16/05–30/05/2027, mâu thuẫn 'cừ luân chuyển 3 lần'",
                "Ảnh hưởng trực tiếp H1 (số ngày thuê)", None, None, None])
    out.append(["H6", "low", "Nhổ cừ áp AC.23420 'dưới nước' cho công trình trên cạn; cọc tre L=2,5 m áp AC.12121 '> 2,5 m'",
                "Có thể bị cắt giảm khi thẩm tra quyết toán", None, None, None])
    return out


HEAD = Font(bold=True, color="FFFFFF")
FILL = PatternFill("solid", fgColor="1E293B")


def _header(ws, cols: list[str], widths: list[int]):
    ws.append(cols)
    for i, w in enumerate(widths, 1):
        c = ws.cell(row=ws.max_row, column=i)
        c.font, c.fill, c.alignment = HEAD, FILL, Alignment(wrap_text=True, vertical="center")
        ws.column_dimensions[c.column_letter].width = w
    ws.freeze_panes = ws.cell(row=ws.max_row + 1, column=1)


def export_workbook(est: Estimate, out: Path, contract_value: Decimal = CONTRACT_VALUE, annex: Path | None = None) -> Path:
    out = Path(out)
    out.parent.mkdir(parents=True, exist_ok=True)
    wb = openpyxl.Workbook()

    ws = wb.active
    ws.title = "Du toan TD"
    _header(ws, ["HM", "Mục", "STT", "Mã ĐM", "Tên công tác", "ĐV", "Khối lượng", "ĐG VL", "ĐG VL phụ", "ĐG NC", "ĐG Máy",
                 "TT VL", "TT VL phụ", "TT NC", "TT Máy", "Cộng trực tiếp", "Diễn giải KL (TĐ)"],
            [5, 22, 5, 11, 60, 8, 12, 13, 11, 13, 13, 15, 13, 15, 15, 16, 70])
    for k, it in est.all_items():
        ws.append([k + 1, it.section, it.stt, it.code, it.name, it.unit, float(it.qty),
                   *[float(it.dg[x]) for x in ("vl", "vlp", "nc", "m")], *[float(it.tt[x]) for x in ("vl", "vlp", "nc", "m")],
                   float(it.direct_total), " | ".join(it.explain)])
    for row in ws.iter_rows(min_row=2, min_col=7, max_col=16):
        for c in row:
            c.number_format = "#,##0.000" if c.column == 7 else "#,##0"

    ws = wb.create_sheet("THKP")
    _header(ws, ["Hạng mục", "STT", "Nội dung chi phí", "Ký hiệu", "Cách tính", "Hệ số", "Giá trị (đ)"], [40, 6, 55, 10, 22, 8, 18])
    for p in est.packages:
        for stt, content, sym, how, coef, val in p.thkp_rows:
            ws.append([p.name[:60], stt, content, sym, how, coef, float(val)])
    ws.append([])
    ws.append(["TỔNG DỰ TOÁN TĐ (2 hạng mục, làm tròn)", None, None, None, None, None, float(est.grand_total)])
    for row in ws.iter_rows(min_row=2, min_col=7, max_col=7):
        row[0].number_format = "#,##0"

    ws = wb.create_sheet("Doi chieu HD")
    k = est.contract_factor(contract_value)
    ws.append(["Giá HĐ (Phụ lục 01, làm tròn)", float(contract_value)])
    ws.append(["Dự toán TĐ (làm tròn)", float(est.grand_total)])
    ws.append(["Hệ số HĐ/TĐ", k])
    ws.append([])
    _header(ws, ["HM", "STT", "Mã TĐ", "Mã HĐ", "Tên công tác (TĐ)", "ĐV", "KL TĐ", "KL HĐ", "Chênh KL", "ĐG trực tiếp TĐ",
                 "ĐG HĐ (đã gồm chi phí chung, TL, VAT…)", "Thành tiền HĐ", "Ghi chú"],
            [5, 5, 11, 11, 60, 8, 12, 12, 11, 15, 18, 17, 30])
    hd_rows = load_contract_annex(annex) if annex and Path(annex).exists() else []
    td_items = est.all_items()
    for pk, (it, h) in align_with_contract(est, hd_rows):
        if it is None:  # dòng chỉ có trong HĐ
            ws.append([pk + 1, h["stt"], None, h["code"], h["name"], h["unit"], None, float(h["qty"]), None, None,
                       float(h["unit_price"]), float(h["amount"]), "chỉ có trong HĐ"])
            continue
        note = []
        if h is None:
            note.append("chỉ có trong TĐ (HĐ gộp/bỏ)")
        elif abs(h["qty"] - it.qty) > Decimal("0.0005"):
            note.append("KL khác")
        ws.append([pk + 1, it.stt, it.code, h["code"] if h else None, it.name, it.unit, float(it.qty),
                   float(h["qty"]) if h else None, float(h["qty"] - it.qty) if h else None, float(it.unit_direct),
                   float(h["unit_price"]) if h else None, float(h["amount"]) if h else None, ", ".join(note) or "khớp"])
    hd_sum = sum((h["amount"] for h in hd_rows), Decimal("0"))
    ws.append([])
    ws.append([None, None, None, None, "Tổng thành tiền các dòng HĐ (chưa làm tròn)", None, None, None, None, None, None, float(hd_sum)])

    ws = wb.create_sheet("Rui ro H1-H6")
    _header(ws, ["Mã", "Mức", "Vấn đề", "Cơ sở / giả định", "Chi phí trực tiếp trong TĐ (đ)", "Chi phí trực tiếp theo kịch bản (đ)",
                 "Chênh (đ, trước chi phí chung/TL/VAT)"], [5, 8, 60, 70, 18, 18, 20])
    for row in risk_rows(est):
        ws.append(row)
    for row in ws.iter_rows(min_row=2, min_col=5, max_col=7):
        for c in row:
            c.number_format = "#,##0"

    ws = wb.create_sheet("Nguon")
    for line in [
        ["Dự toán TĐ", str(est.source.name), "02. DToan thanh dinh ba Lanh.xls (chuyển xlsx bằng Excel COM, không sửa)"],
        ["Phụ lục 01 HĐ", str(Path(annex).name) if annex else "-", "Gói TCXL\\phu luc 01&02.xlsx"],
        ["Kiểm tra", "Tổng TT từng dòng = A1/B1/C1 THKP", "tests/test_ong_den_estimate_rebuild.py"],
        ["Lưu ý", "Các con số kịch bản H1–H3 là ước tính để chuẩn bị phát sinh, không phải giá trị được duyệt", ""],
    ]:
        ws.append(line)
    ws.column_dimensions["A"].width, ws.column_dimensions["B"].width, ws.column_dimensions["C"].width = 16, 60, 70

    wb.save(out)
    return out


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    ap.add_argument("--td", type=Path, default=DEFAULT_TD)
    ap.add_argument("--hd", type=Path, default=DEFAULT_HD)
    ap.add_argument("--out", type=Path, default=DEFAULT_OUT)
    a = ap.parse_args()
    est = load_appraised_estimate(a.td)
    out = export_workbook(est, a.out, annex=a.hd)
    q = est.key_quantities()
    print(f"TĐ: {est.grand_total:,.0f} đ | HĐ/TĐ = {est.contract_factor(CONTRACT_VALUE):.6f} | "
          f"M250 {q['be_tong_m250_m3']:.3f} m3 | thép {q['cot_thep_tan']:.3f} t | -> {out}")


if __name__ == "__main__":
    main()
