import json
import re
import shutil
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path
from openpyxl import load_workbook

from tools.doc_generator.logic.calculations import apply_calculations
from tools.doc_generator.logic.core_fields import merge_core_fields
from tools.doc_generator.logic.document_types import get_document_type

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_ROOT = BASE_DIR.parent.parent
CONTENT_DIR = BASE_DIR / "content"

EXCEL_MAPS_DIR = CONTENT_DIR / "excel_maps"
TEMPLATES_DIR = CONTENT_DIR / "templates"
EXCEL_TEMPLATE_DIR = TEMPLATES_DIR / "legacy_excel"
OLD_WORD_DOC_GENERATOR_DIR = Path("/mnt/storage/sftp/mcelwain/repository/word-doc-generator")
EXPORTS_DIR = PROJECT_ROOT / "exports"

MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
ET.register_namespace("", MAIN_NS)


def safe_filename(value: str) -> str:
    value = str(value or "legacy_export").strip()
    value = re.sub(r"[^A-Za-z0-9._ -]+", "", value)
    value = re.sub(r"\s+", "_", value)
    return value or "legacy_export"


def get_excel_map_set_id(document_type_id: str) -> str:
    document_type = get_document_type(document_type_id)
    return document_type.get("excelMapSet") or document_type_id


def load_excel_map_set(document_type_id: str) -> dict:
    map_set_id = get_excel_map_set_id(document_type_id)
    path = EXCEL_MAPS_DIR / f"{map_set_id}.json"

    if not path.exists():
        raise FileNotFoundError(f"Excel map set not found: {path}")

    return json.loads(path.read_text(encoding="utf-8"))


def load_excel_maps(document_type_id: str) -> list[dict]:
    return load_excel_map_set(document_type_id).get("maps", [])


def load_excel_map(document_type_id: str, map_id: str) -> dict:
    for item in load_excel_maps(document_type_id):
        if item.get("id") == map_id:
            return item

    raise FileNotFoundError(f"Excel map not found for {document_type_id}: {map_id}")


def resolve_excel_template(template_name: str) -> Path:
    template_name = str(template_name or "").strip()
    template_basename = Path(template_name).name

    # Prefer explicit profile-library template paths from the map file.
    candidate_paths = [
        TEMPLATES_DIR / template_name,
        CONTENT_DIR / "templates" / template_name,

        # Backward-compatible fallbacks.
        EXCEL_TEMPLATE_DIR / template_basename,
        OLD_WORD_DOC_GENERATOR_DIR / template_basename,
    ]

    # Last-resort recursive lookups.
    candidate_paths.extend(sorted(TEMPLATES_DIR.rglob(template_basename)))
    candidate_paths.extend(sorted(OLD_WORD_DOC_GENERATOR_DIR.rglob(template_basename)))

    template_path = next((candidate for candidate in candidate_paths if candidate.exists()), None)

    if template_path is None:
        searched = "\n".join(str(candidate) for candidate in candidate_paths[:30])
        raise FileNotFoundError(f"Excel template not found: {template_name}. Searched:\n{searched}")

    return template_path


def cell_parts(cell_ref: str):
    col = "".join(ch for ch in cell_ref if ch.isalpha()).upper()
    row = int("".join(ch for ch in cell_ref if ch.isdigit()))
    return col, row


def col_number(col: str) -> int:
    total = 0
    for ch in col:
        total = total * 26 + (ord(ch) - ord("A") + 1)
    return total


def cell_sort_key(cell_ref: str):
    col, row = cell_parts(cell_ref)
    return row, col_number(col)


def find_or_create_row(sheet_data, row_number: int):
    ns = f"{{{MAIN_NS}}}"
    rows = sheet_data.findall(ns + "row")

    for row in rows:
        if int(row.attrib.get("r", "0")) == row_number:
            return row

    new_row = ET.Element(ns + "row", {"r": str(row_number)})

    inserted = False
    for index, row in enumerate(rows):
        if int(row.attrib.get("r", "0")) > row_number:
            sheet_data.insert(index, new_row)
            inserted = True
            break

    if not inserted:
        sheet_data.append(new_row)

    return new_row


def find_or_create_cell(row, cell_ref: str):
    ns = f"{{{MAIN_NS}}}"
    cells = row.findall(ns + "c")

    for cell in cells:
        if cell.attrib.get("r") == cell_ref:
            return cell

    new_cell = ET.Element(ns + "c", {"r": cell_ref})

    inserted = False
    for index, cell in enumerate(cells):
        if cell_sort_key(cell.attrib.get("r", "A1")) > cell_sort_key(cell_ref):
            row.insert(index, new_cell)
            inserted = True
            break

    if not inserted:
        row.append(new_cell)

    return new_cell


def set_cell_text(cell, value):
    ns = f"{{{MAIN_NS}}}"

    for child in list(cell):
        cell.remove(child)

    cell.attrib.pop("s", None)
    cell.attrib["t"] = "inlineStr"

    inline = ET.SubElement(cell, ns + "is")
    text = ET.SubElement(inline, ns + "t")
    text.text = "" if value is None else str(value)


def write_sheet1_values(xlsx_path: Path, output_path: Path, field_to_cell: dict, data: dict):
    workbook = load_workbook(xlsx_path)
    worksheet = workbook.worksheets[0]

    for field, cell_ref in field_to_cell.items():
        worksheet[cell_ref] = "" if data.get(field) is None else data.get(field, "")

    # Ask Excel-compatible apps to recalculate formulas when the workbook opens.
    try:
        workbook.calculation.fullCalcOnLoad = True
        workbook.calculation.forceFullCalc = True
    except Exception:
        pass

    workbook.save(output_path)


def export_profile_excel(document_type_id: str, data: dict, map_id: str) -> Path:
    document_type = get_document_type(document_type_id)

    final_data = merge_core_fields(data)
    final_data = apply_calculations(document_type, final_data)

    excel_map = load_excel_map(document_type_id, map_id)
    template_name = excel_map.get("template") or "template.xlsx"
    template_path = resolve_excel_template(template_name)

    EXPORTS_DIR.mkdir(parents=True, exist_ok=True)

    case_number = safe_filename(final_data.get("caseNumber") or "no_case_number")
    timestamp = safe_filename(final_data.get("timestamp_YYYY-MM-DD_HH-mm-ss") or "export")
    output_path = EXPORTS_DIR / f"{document_type_id}_{map_id}_{case_number}_{timestamp}.xlsx"

    write_sheet1_values(
        xlsx_path=template_path,
        output_path=output_path,
        field_to_cell=excel_map.get("fields", {}),
        data=final_data,
    )

    return output_path


# Backward-compatible function name while routes are transitioning.
def export_legacy_excel(document_type_id: str, data: dict, map_id: str = "legacy_datafile") -> Path:
    return export_profile_excel(document_type_id, data, map_id)
