from __future__ import annotations

from io import BytesIO
from typing import Any

from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.modules.services.models import FirmServiceTaskTemplate, ServiceCatalogue


def build_import_template_workbook() -> bytes:
    wb = Workbook()

    ws_services = wb.active
    ws_services.title = "service_catalogue"
    service_headers = [
        "service_code",
        "service_name",
        "category",
        "description",
        "recurrence_type",
        "is_active",
        "is_client_requestable",
        "is_consultant_requestable",
    ]
    ws_services.append(service_headers)
    ws_services.append([
        "GST-MONTHLY",
        "GST Monthly Return Filing",
        "GST",
        "Monthly GST compliance service",
        "MONTHLY",
        "TRUE",
        "TRUE",
        "FALSE",
    ])

    ws_tasks = wb.create_sheet("firm_task_templates")
    task_headers = [
        "service_code",
        "sequence_no",
        "task_name",
        "description",
        "default_role_name",
        "sla_days",
        "is_mandatory",
        "requires_review",
        "is_active",
    ]
    ws_tasks.append(task_headers)
    ws_tasks.append([
        "GST-MONTHLY",
        1,
        "Collect Purchase and Sales Data",
        "Collect source data from client",
        "Staff",
        3,
        "TRUE",
        "FALSE",
        "TRUE",
    ])
    ws_tasks.append([
        "GST-MONTHLY",
        2,
        "Review and File Return",
        "Manager review and final filing",
        "Partner",
        2,
        "TRUE",
        "TRUE",
        "TRUE",
    ])

    for ws in [ws_services, ws_tasks]:
        for cell in ws[1]:
            cell.font = Font(bold=True)
        for col in ws.columns:
            max_len = 0
            col_letter = col[0].column_letter
            for cell in col:
                val = "" if cell.value is None else str(cell.value)
                max_len = max(max_len, len(val))
            ws.column_dimensions[col_letter].width = min(max(max_len + 2, 14), 40)

    out = BytesIO()
    wb.save(out)
    return out.getvalue()


def _norm_text(value: Any) -> str:
    if value is None:
        return ""
    return str(value).strip()


def _norm_upper(value: Any) -> str:
    return _norm_text(value).upper()


def _norm_bool(value: Any, default: bool = False) -> bool:
    if value is None or value == "":
        return default
    if isinstance(value, bool):
        return value
    return str(value).strip().lower() in {"1", "true", "yes", "y", "on"}


def _norm_int(value: Any, default: int | None = None) -> int | None:
    if value is None or value == "":
        return default
    try:
        return int(value)
    except Exception:
        return default


def _sheet_headers(ws) -> dict[str, int]:
    headers = {}
    first_row = next(ws.iter_rows(min_row=1, max_row=1, values_only=True), [])
    for idx, val in enumerate(first_row):
        key = _norm_text(val).lower()
        if key:
            headers[key] = idx
    return headers


def _cell(row: tuple, headers: dict[str, int], key: str) -> Any:
    idx = headers.get(key.lower())
    if idx is None or idx >= len(row):
        return None
    return row[idx]


def parse_import_workbook(file_bytes: bytes) -> dict:
    wb = load_workbook(BytesIO(file_bytes), data_only=True)
    errors: list[str] = []
    catalogue_rows: list[dict] = []
    task_rows: list[dict] = []

    if "service_catalogue" not in wb.sheetnames:
        return {"ok": False, "errors": ["Workbook must contain a sheet named 'service_catalogue'."], "catalogue_rows": [], "task_rows": []}

    ws_services = wb["service_catalogue"]
    headers = _sheet_headers(ws_services)
    for h in ["service_code", "service_name"]:
        if h not in headers:
            errors.append(f"Service catalogue sheet missing required column: {h}")

    for row_no, row in enumerate(ws_services.iter_rows(min_row=2, values_only=True), start=2):
        service_code = _norm_upper(_cell(row, headers, "service_code"))
        service_name = _norm_text(_cell(row, headers, "service_name"))
        if not service_code and not service_name:
            continue
        if not service_code:
            errors.append(f"Service catalogue row {row_no}: service_code is required.")
            continue
        if not service_name:
            errors.append(f"Service catalogue row {row_no}: service_name is required.")
            continue
        catalogue_rows.append({
            "service_code": service_code,
            "service_name": service_name,
            "category": _norm_text(_cell(row, headers, "category")) or None,
            "description": _norm_text(_cell(row, headers, "description")) or None,
            "recurrence_type": _norm_text(_cell(row, headers, "recurrence_type")) or None,
            "is_active": _norm_bool(_cell(row, headers, "is_active"), True),
            "is_client_requestable": _norm_bool(_cell(row, headers, "is_client_requestable"), False),
            "is_consultant_requestable": _norm_bool(_cell(row, headers, "is_consultant_requestable"), False),
        })

    if "firm_task_templates" in wb.sheetnames:
        ws_tasks = wb["firm_task_templates"]
        task_headers = _sheet_headers(ws_tasks)
        for h in ["service_code", "sequence_no", "task_name"]:
            if h not in task_headers:
                errors.append(f"Firm task templates sheet missing required column: {h}")
        for row_no, row in enumerate(ws_tasks.iter_rows(min_row=2, values_only=True), start=2):
            service_code = _norm_upper(_cell(row, task_headers, "service_code"))
            task_name = _norm_text(_cell(row, task_headers, "task_name"))
            sequence_no = _norm_int(_cell(row, task_headers, "sequence_no"))
            if not service_code and not task_name:
                continue
            if not service_code:
                errors.append(f"Firm task templates row {row_no}: service_code is required.")
                continue
            if not task_name:
                errors.append(f"Firm task templates row {row_no}: task_name is required.")
                continue
            if sequence_no is None:
                errors.append(f"Firm task templates row {row_no}: sequence_no must be numeric.")
                continue
            task_rows.append({
                "service_code": service_code,
                "sequence_no": sequence_no,
                "task_name": task_name,
                "description": _norm_text(_cell(row, task_headers, "description")) or None,
                "default_role_name": _norm_text(_cell(row, task_headers, "default_role_name")) or None,
                "sla_days": _norm_int(_cell(row, task_headers, "sla_days")),
                "is_mandatory": _norm_bool(_cell(row, task_headers, "is_mandatory"), True),
                "requires_review": _norm_bool(_cell(row, task_headers, "requires_review"), False),
                "is_active": _norm_bool(_cell(row, task_headers, "is_active"), True),
            })

    service_codes = {row["service_code"] for row in catalogue_rows}
    for row in task_rows:
        if row["service_code"] not in service_codes:
            errors.append(f"Task row for service_code '{row['service_code']}' does not match any service in service_catalogue sheet.")

    return {"ok": len(errors) == 0, "errors": errors, "catalogue_rows": catalogue_rows, "task_rows": task_rows}


def apply_import_payload(
    db: Session,
    *,
    tenant_id: int,
    branch_id: int | None,
    actor_user_id: int,
    catalogue_rows: list[dict],
    task_rows: list[dict],
) -> dict:
    created_catalogue = 0
    updated_catalogue = 0
    created_tasks = 0
    updated_tasks = 0
    catalogue_map: dict[str, ServiceCatalogue] = {}

    for row in catalogue_rows:
        service = db.execute(select(ServiceCatalogue).where(ServiceCatalogue.service_code == row["service_code"])).scalar_one_or_none()
        if service is None:
            service = ServiceCatalogue(
                service_code=row["service_code"],
                service_name=row["service_name"],
                category=row["category"],
                description=row["description"],
                recurrence_type=row["recurrence_type"],
                is_active=row["is_active"],
                is_client_requestable=row["is_client_requestable"],
                is_consultant_requestable=row["is_consultant_requestable"],
                created_by_user_id=actor_user_id,
                updated_by_user_id=actor_user_id,
            )
            db.add(service)
            db.flush()
            created_catalogue += 1
        else:
            service.service_name = row["service_name"]
            service.category = row["category"]
            service.description = row["description"]
            service.recurrence_type = row["recurrence_type"]
            service.is_active = row["is_active"]
            service.is_client_requestable = row["is_client_requestable"]
            service.is_consultant_requestable = row["is_consultant_requestable"]
            service.updated_by_user_id = actor_user_id
            updated_catalogue += 1
        catalogue_map[row["service_code"]] = service

    for row in task_rows:
        service = catalogue_map[row["service_code"]]
        task = db.execute(
            select(FirmServiceTaskTemplate).where(
                FirmServiceTaskTemplate.tenant_id == tenant_id,
                FirmServiceTaskTemplate.service_catalogue_id == service.id,
                FirmServiceTaskTemplate.sequence_no == row["sequence_no"],
            )
        ).scalar_one_or_none()
        if task is None:
            task = FirmServiceTaskTemplate(
                tenant_id=tenant_id,
                branch_id=branch_id,
                service_catalogue_id=service.id,
                sequence_no=row["sequence_no"],
                task_name=row["task_name"],
                description=row["description"],
                default_role_name=row["default_role_name"],
                sla_days=row["sla_days"],
                is_mandatory=row["is_mandatory"],
                requires_review=row["requires_review"],
                is_active=row["is_active"],
                created_by_user_id=actor_user_id,
                updated_by_user_id=actor_user_id,
            )
            db.add(task)
            created_tasks += 1
        else:
            task.task_name = row["task_name"]
            task.description = row["description"]
            task.default_role_name = row["default_role_name"]
            task.sla_days = row["sla_days"]
            task.is_mandatory = row["is_mandatory"]
            task.requires_review = row["requires_review"]
            task.is_active = row["is_active"]
            task.branch_id = branch_id
            task.updated_by_user_id = actor_user_id
            updated_tasks += 1

    db.commit()
    return {
        "created_catalogue": created_catalogue,
        "updated_catalogue": updated_catalogue,
        "created_tasks": created_tasks,
        "updated_tasks": updated_tasks,
    }
