import os
import re
from docxtpl import DocxTemplate
from jinja2 import Environment


def _coerce_number(value) -> float:
    if value is None:
        return 0.0
    if isinstance(value, (int, float)):
        try:
            return float(value)
        except Exception:
            return 0.0

    text = str(value).strip()
    if not text:
        return 0.0
    text = (
        text.replace("VND", "")
        .replace("VNĐ", "")
        .replace("VNÐ", "")
        .replace("đ", "")
        .replace("Đ", "")
        .replace("%", "")
        .replace("\u00a0", "")
        .replace(" ", "")
    )

    sci_text = text.replace(",", ".")
    if re.fullmatch(r"[+-]?(?:\d+(?:\.\d*)?|\.\d+)[eE][+-]?\d+", sci_text):
        try:
            return float(sci_text)
        except Exception:
            return 0.0

    text = re.sub(r"[^0-9,.\-]", "", text)
    if not text or text in {"-", ".", ","}:
        return 0.0

    if "," in text and "." in text:
        decimal_sep = "," if text.rfind(",") > text.rfind(".") else "."
        thousands_sep = "." if decimal_sep == "," else ","
        text = text.replace(thousands_sep, "").replace(decimal_sep, ".")
    elif "," in text:
        parts = text.split(",")
        if len(parts) > 2 or (len(parts[-1]) == 3 and all(part.isdigit() for part in parts if part)):
            text = "".join(parts)
        else:
            text = text.replace(",", ".")
    elif "." in text:
        parts = text.split(".")
        if len(parts) > 2 or (len(parts[-1]) == 3 and all(part.isdigit() for part in parts if part)):
            text = "".join(parts)

    try:
        return float(text)
    except Exception:
        return 0.0


def _format_plain_number(value, max_decimals: int = 2, group: bool = False, group_sep: str = ",") -> str:
    number = _coerce_number(value)
    if abs(number) < 1e-12:
        number = 0.0
    if float(number).is_integer():
        text = f"{int(round(number)):,}" if group else str(int(round(number)))
    else:
        decimals = max(0, int(max_decimals))
        text = f"{number:,.{decimals}f}" if group else f"{number:.{decimals}f}"
        text = text.rstrip("0").rstrip(".")
    return text.replace(",", group_sep) if group_sep != "," else text


def generate_contract(template_path, output_path, context):
    """
    Generates a Word document from a template using the provided context.
    :param template_path: Path to the .docx template.
    :param output_path: Path to save the generated document.
    :param context: Dictionary containing data to replace tags.
    """
    if not os.path.exists(template_path):
        raise FileNotFoundError(f"Template not found: {template_path}")
    
    doc = DocxTemplate(template_path)

    # Cấu hình jinja2 để hỗ trợ bảng với {% tr for %} và không bị crash nếu biến thiếu
    # Do docxtpl tạo jinja_env lúc render, ta truyền thẳng jinja_env custom vào
    jinja_env = Environment()
    jinja_env.undefined = _make_lenient_undefined()

    try:
        doc.render(context, jinja_env=jinja_env)
    except Exception as e:
        err_msg = str(e)
        if "unknown tag" in err_msg.lower():
            raise ValueError(
                f"Lỗi cú pháp trong mẫu hợp đồng:\n{err_msg}\n\n"
                "Trong phần bảng của mẫu Word, bắt buộc dùng:\n"
                "  {%tr for item in ds_san_pham %}\n"
                "  ... nội dung hàng ...\n"
                "  {%tr endfor %}\n\n"
                "Tuyệt đối không dùng {% for %} bên trong bảng."
            ) from e
        raise
    
    doc.save(output_path)
    return output_path


def _make_lenient_undefined():
    """Trả về Undefined class chấp nhận mọi biến không tồn tại (trả về chuỗi rỗng)."""
    from jinja2 import Undefined
    
    class SilentUndefined(Undefined):
        def _fail_with_undefined_error(self, *args, **kwargs):
            return ""
        __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
            __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
            __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
            __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
            __float__ = __complex__ = __pow__ = __rpow__ = \
            _fail_with_undefined_error
        def __str__(self):
            return ""
        def __repr__(self):
            return ""
    
    return SilentUndefined


def format_product_list(products):
    """
    Ensures products have the expected keys for the Word table.
    Expects list of dicts with: stt, ten_sp, dvt, so_luong, don_gia, vat_rate, thanh_tien
    """
    formatted = []
    for item in products:
        quantity = _coerce_number(item.get('so_luong', 0))
        price = _coerce_number(item.get('don_gia', 0))
        vat_rate = _coerce_number(item.get('vat_rate', 0))
        amount = _coerce_number(item.get('thanh_tien', 0))
        formatted.append({
            'stt': item.get('stt', ''),
            'ten_sp': item.get('ten_sp', ''),
            'dvt': item.get('dvt', ''),
            'so_luong': _format_plain_number(quantity),
            'so_luong_fmt': _format_plain_number(quantity),
            'don_gia': _format_plain_number(price, max_decimals=0, group=True, group_sep='.'),
            'don_gia_fmt': _format_plain_number(price, max_decimals=0, group=True, group_sep='.'),
            'vat_rate': _format_plain_number(vat_rate),
            'vat_rate_fmt': _format_plain_number(vat_rate),
            'thanh_tien': _format_plain_number(amount, max_decimals=0, group=True, group_sep='.'),
            'thanh_tien_fmt': _format_plain_number(amount, max_decimals=0, group=True, group_sep='.')
        })
    return formatted
