from __future__ import annotations

from datetime import datetime
import json
import mimetypes
from pathlib import Path
from urllib.parse import urlencode
from urllib.error import HTTPError, URLError
from urllib.parse import urljoin
from urllib.request import Request
import uuid

from app_core.http_client import open_url
from app_core.paths import DEBUG_DIR


def _timeout_seconds(settings: dict) -> int:
    return max(1, int(settings.get("request_timeout_seconds", 8) or 8))


def _api_base_url(settings: dict) -> str:
    return str(settings.get("api_base_url", "")).strip().rstrip("/")


def _build_api_url(settings: dict, path: str) -> str:
    base_url = _api_base_url(settings)
    if not base_url:
        raise ValueError("API Base URL is empty.")
    normalized_path = path if path.startswith("/") else f"/{path}"
    return urljoin(base_url + "/", normalized_path.lstrip("/"))


def _read_json_response(response) -> dict:
    charset = response.headers.get_content_charset() or "utf-8"
    payload = json.loads(response.read().decode(charset, errors="replace"))
    if not isinstance(payload, dict):
        raise ValueError("Response JSON must be an object.")
    return payload


def _append_api_debug_log(message: str) -> None:
    try:
        DEBUG_DIR.mkdir(parents=True, exist_ok=True)
        log_path = DEBUG_DIR / "tech_pack_save_debug.log"
        with log_path.open("a", encoding="utf-8") as handle:
            handle.write(f"[{datetime.now().isoformat(timespec='seconds')}] API {message}\n")
    except Exception:
        pass


def _request_json(
    settings: dict,
    method: str,
    path: str,
    payload: dict | None = None,
    *,
    timeout_seconds: int | None = None,
) -> dict:
    url = _build_api_url(settings, path)
    body: bytes | None = None
    headers = {
        "User-Agent": "CitiFoxERPApp/desktop",
        "Accept": "application/json",
    }
    if payload is not None:
        body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
        headers["Content-Type"] = "application/json; charset=utf-8"
        if path == "/api/tech-pack/record-patch":
            _append_api_debug_log(f"request path={path} method={method.upper()} body_bytes={len(body)}")
    request = Request(url, data=body, headers=headers, method=method.upper())
    with open_url(request, timeout=timeout_seconds or _timeout_seconds(settings)) as response:
        return _read_json_response(response)


def request_json(
    settings: dict,
    method: str,
    path: str,
    payload: dict | None = None,
    *,
    timeout_seconds: int | None = None,
) -> dict:
    try:
        return _request_json(settings, method, path, payload, timeout_seconds=timeout_seconds)
    except HTTPError as exc:
        if path == "/api/tech-pack/record-patch":
            _append_api_debug_log(f"http-error path={path} code={exc.code} reason={exc.reason}")
        try:
            error_payload = _read_json_response(exc)
            detail = error_payload.get("detail", exc.reason)
        except Exception:
            detail = exc.reason
        raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
    except URLError as exc:
        raise RuntimeError(f"Connection failed: {exc.reason}") from exc
    except Exception as exc:
        raise RuntimeError(str(exc)) from exc


def upload_shared_file(settings: dict, file_path: str | Path, *, bucket: str = "uploads") -> dict:
    source_path = Path(str(file_path or "").strip())
    if not source_path.exists() or not source_path.is_file():
        raise RuntimeError(f"File not found: {source_path}")
    filename = source_path.name
    upload_id = uuid.uuid4().hex
    chunk_size = 128 * 1024
    file_size = source_path.stat().st_size
    total_chunks = max(1, (file_size + chunk_size - 1) // chunk_size)
    with source_path.open("rb") as source:
        for chunk_index in range(total_chunks):
            chunk_bytes = source.read(chunk_size)
            _post_multipart_file(
                settings,
                "/api/shared/upload-chunk",
                fields=[
                    ("upload_id", upload_id),
                    ("bucket", str(bucket or "uploads").strip() or "uploads"),
                    ("filename", filename),
                    ("chunk_index", str(chunk_index)),
                    ("total_chunks", str(total_chunks)),
                ],
                file_field_name="file",
                filename=f"{filename}.part",
                content_type="application/octet-stream",
                file_bytes=chunk_bytes,
                timeout_seconds=max(20, _timeout_seconds(settings)),
            )
    return request_json(
        settings,
        "POST",
        "/api/shared/upload-complete",
        {"upload_id": upload_id},
    )


def _post_multipart_file(
    settings: dict,
    path: str,
    *,
    fields: list[tuple[str, str]] | None = None,
    file_field_name: str,
    filename: str,
    content_type: str,
    file_bytes: bytes,
    timeout_seconds: int | None = None,
) -> dict:
    url = _build_api_url(settings, path)
    boundary = f"----SunnyERPBoundary{uuid.uuid4().hex}"
    body = bytearray()
    for field_name, field_value in list(fields or []):
        body.extend(f"--{boundary}\r\n".encode("utf-8"))
        body.extend(f'Content-Disposition: form-data; name="{field_name}"\r\n\r\n'.encode("utf-8"))
        body.extend(str(field_value).encode("utf-8"))
        body.extend(b"\r\n")
    body.extend(f"--{boundary}\r\n".encode("utf-8"))
    body.extend(
        (
            f'Content-Disposition: form-data; name="{file_field_name}"; filename="{filename}"\r\n'
            f"Content-Type: {content_type}\r\n\r\n"
        ).encode("utf-8")
    )
    body.extend(file_bytes)
    body.extend(b"\r\n")
    body.extend(f"--{boundary}--\r\n".encode("utf-8"))
    request = Request(
        url,
        data=bytes(body),
        headers={
            "User-Agent": "CitiFoxERPApp/desktop",
            "Accept": "application/json",
            "Content-Type": f"multipart/form-data; boundary={boundary}",
        },
        method="POST",
    )
    try:
        with open_url(request, timeout=timeout_seconds or _timeout_seconds(settings)) as response:
            return _read_json_response(response)
    except HTTPError as exc:
        try:
            error_payload = _read_json_response(exc)
            detail = error_payload.get("detail", exc.reason)
        except Exception:
            detail = exc.reason
        raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
    except URLError as exc:
        raise RuntimeError(f"Connection failed: {exc.reason}") from exc
    except Exception as exc:
        raise RuntimeError(str(exc)) from exc


def import_tech_pack_source_file(settings: dict, file_path: str | Path, *, target_style_code: str = "") -> dict:
    source_path = Path(str(file_path or "").strip())
    if not source_path.exists() or not source_path.is_file():
        raise RuntimeError(f"File not found: {source_path}")
    filename = source_path.name
    upload_id = uuid.uuid4().hex
    chunk_size = 128 * 1024
    file_size = source_path.stat().st_size
    total_chunks = max(1, (file_size + chunk_size - 1) // chunk_size)
    with source_path.open("rb") as source:
        for chunk_index in range(total_chunks):
            chunk_bytes = source.read(chunk_size)
            _post_multipart_file(
                settings,
                "/api/tech-pack/import-source-chunk",
                fields=[
                    ("upload_id", upload_id),
                    ("filename", filename),
                    ("chunk_index", str(chunk_index)),
                    ("total_chunks", str(total_chunks)),
                ],
                file_field_name="file",
                filename=f"{filename}.part",
                content_type="application/octet-stream",
                file_bytes=chunk_bytes,
                timeout_seconds=max(20, _timeout_seconds(settings)),
            )
    return request_json(
        settings,
        "POST",
        "/api/tech-pack/import-source-complete",
        {
            "upload_id": upload_id,
            "filename": filename,
            "total_chunks": total_chunks,
            "target_style_code": str(target_style_code or "").strip(),
        },
        timeout_seconds=max(180, _timeout_seconds(settings)),
    )


def fetch_tech_pack_import_jobs(settings: dict, job_ids: list[str]) -> list[dict]:
    normalized_job_ids = [str(item).strip() for item in job_ids if str(item).strip()]
    query = urlencode({"job_ids": ",".join(normalized_job_ids)}) if normalized_job_ids else ""
    path = "/api/tech-pack/import-jobs"
    if query:
        path = f"{path}?{query}"
    payload = request_json(settings, "GET", path)
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def fetch_orders(settings: dict, *, limit: int = 5000) -> list[dict]:
    payload = request_json(settings, "GET", f"/api/orders?limit={max(1, min(int(limit), 5000))}")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def authenticate_user(settings: dict, email: str, password: str) -> dict | None:
    try:
        payload = request_json(
            settings,
            "POST",
            "/api/auth/login",
            {"email": str(email or "").strip(), "password": str(password or "")},
        )
    except RuntimeError as exc:
        if "HTTP 401" in str(exc):
            return None
        raise
    user = payload.get("user")
    return dict(user) if isinstance(user, dict) else None


def register_user(settings: dict, *, email: str, password: str, display_name: str = "", role: str = "user") -> dict:
    return request_json(
        settings,
        "POST",
        "/api/auth/register",
        {
            "email": str(email or "").strip(),
            "password": str(password or ""),
            "display_name": str(display_name or "").strip(),
            "role": str(role or "user").strip().lower() or "user",
        },
    )


def create_password_reset_token(settings: dict, *, email: str, expires_at: str) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/auth/password-reset-token",
        {"email": str(email or "").strip(), "expires_at": str(expires_at or "").strip()},
    )


def get_password_reset_token(settings: dict, token: str) -> dict | None:
    query = urlencode({"token": str(token or "").strip()})
    try:
        payload = request_json(settings, "GET", f"/api/auth/password-reset-token?{query}")
    except RuntimeError as exc:
        if "HTTP 404" in str(exc):
            return None
        raise
    row = payload.get("token_row")
    return dict(row) if isinstance(row, dict) else None


def update_password_with_token(settings: dict, *, token: str, password: str) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/auth/password-reset",
        {"token": str(token or "").strip(), "password": str(password or "")},
    )


def fetch_users(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/users")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def fetch_user_by_email(settings: dict, email: str) -> dict | None:
    query = urlencode({"email": str(email or "").strip()})
    try:
        payload = request_json(settings, "GET", f"/api/user?{query}")
    except RuntimeError as exc:
        if "HTTP 404" in str(exc):
            return None
        raise
    user = payload.get("user")
    return dict(user) if isinstance(user, dict) else None


def update_user_role(settings: dict, *, email: str, role: str) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/users/role",
        {"email": str(email or "").strip(), "role": str(role or "user").strip().lower() or "user"},
    )


def update_user_active_status(settings: dict, *, email: str, is_active: bool) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/users/active",
        {"email": str(email or "").strip(), "is_active": bool(is_active)},
    )


def update_user_approval_status(settings: dict, *, email: str, approval_status: str, approved_by: str = "") -> dict:
    return request_json(
        settings,
        "POST",
        "/api/users/approval",
        {
            "email": str(email or "").strip(),
            "approval_status": str(approval_status or "pending").strip().lower() or "pending",
            "approved_by": str(approved_by or "").strip(),
        },
    )


def delete_users(settings: dict, emails: list[str]) -> dict:
    return request_json(settings, "POST", "/api/users/delete", {"emails": list(emails)})


def fetch_permission_settings(settings: dict) -> dict:
    payload = request_json(settings, "GET", "/api/permissions")
    permissions = payload.get("permissions")
    return dict(permissions) if isinstance(permissions, dict) else {}


def save_permission_settings(settings: dict, permissions: dict) -> dict:
    payload = request_json(settings, "POST", "/api/permissions", {"permissions": permissions})
    saved = payload.get("permissions")
    return dict(saved) if isinstance(saved, dict) else {}


def fetch_folder_contents(settings: dict, *, email: str, path: str = "") -> dict:
    query = urlencode(
        {
            "email": str(email or "").strip(),
            "path": str(path or "").strip(),
        }
    )
    return request_json(settings, "GET", f"/api/folder/contents?{query}")


def download_server_file(settings: dict, *, path: str, destination: str | Path, email: str = "") -> Path:
    query_items = {"path": str(path or "").strip()}
    if str(email or "").strip():
        query_items["email"] = str(email or "").strip()
    url = _build_api_url(settings, f"/api/files?{urlencode(query_items)}")
    destination_path = Path(str(destination or "").strip())
    destination_path.parent.mkdir(parents=True, exist_ok=True)
    request = Request(
        url,
        headers={
            "User-Agent": "CitiFoxERPApp/desktop",
            "Accept": "*/*",
        },
        method="GET",
    )
    try:
        with open_url(request, timeout=max(20, _timeout_seconds(settings))) as response:
            destination_path.write_bytes(response.read())
    except HTTPError as exc:
        try:
            error_payload = _read_json_response(exc)
            detail = error_payload.get("detail", exc.reason)
        except Exception:
            detail = exc.reason
        raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
    except URLError as exc:
        raise RuntimeError(f"Connection failed: {exc.reason}") from exc
    except Exception as exc:
        raise RuntimeError(str(exc)) from exc
    return destination_path


def delete_server_path(settings: dict, *, email: str, path: str) -> dict:
    url = _build_api_url(settings, "/api/files/delete")
    boundary = f"----SunnyERPBoundary{uuid.uuid4().hex}"
    body = bytearray()
    for field_name, field_value in (
        ("email", str(email or "").strip()),
        ("path", str(path or "").strip()),
    ):
        body.extend(f"--{boundary}\r\n".encode("utf-8"))
        body.extend(f'Content-Disposition: form-data; name="{field_name}"\r\n\r\n'.encode("utf-8"))
        body.extend(str(field_value).encode("utf-8"))
        body.extend(b"\r\n")
    body.extend(f"--{boundary}--\r\n".encode("utf-8"))
    request = Request(
        url,
        data=bytes(body),
        headers={
            "User-Agent": "CitiFoxERPApp/desktop",
            "Accept": "application/json",
            "Content-Type": f"multipart/form-data; boundary={boundary}",
        },
        method="POST",
    )
    try:
        with open_url(request, timeout=max(20, _timeout_seconds(settings))) as response:
            return _read_json_response(response)
    except HTTPError as exc:
        try:
            error_payload = _read_json_response(exc)
            detail = error_payload.get("detail", exc.reason)
        except Exception:
            detail = exc.reason
        raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
    except URLError as exc:
        raise RuntimeError(f"Connection failed: {exc.reason}") from exc
    except Exception as exc:
        raise RuntimeError(str(exc)) from exc


def create_server_folder(settings: dict, *, email: str, target_dir: str, folder_name: str, overwrite: bool = False) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/folders/create",
        {
            "email": str(email or "").strip(),
            "target_dir": str(target_dir or "").strip(),
            "folder_name": str(folder_name or "").strip(),
            "overwrite": bool(overwrite),
        },
    )


def upload_server_file(
    settings: dict,
    *,
    email: str,
    target_dir: str,
    source_path: str | Path,
    overwrite: bool = False,
    relative_path: str = "",
) -> dict:
    file_path = Path(str(source_path or "").strip())
    if not file_path.exists() or not file_path.is_file():
        raise RuntimeError(f"File not found: {file_path}")
    return _post_multipart_file(
        settings,
        "/api/files/upload",
        fields=[
            ("email", str(email or "").strip()),
            ("target_dir", str(target_dir or "").strip()),
            ("overwrite", "true" if overwrite else "false"),
            ("relative_path", str(relative_path or "").strip()),
        ],
        file_field_name="file",
        filename=file_path.name,
        content_type=mimetypes.guess_type(file_path.name)[0] or "application/octet-stream",
        file_bytes=file_path.read_bytes(),
        timeout_seconds=max(60, _timeout_seconds(settings)),
    )


def fetch_app_setting(settings: dict, setting_key: str, default: object = None) -> object:
    query = urlencode({"key": str(setting_key or "").strip()})
    try:
        payload = request_json(settings, "GET", f"/api/app-setting?{query}")
    except RuntimeError as exc:
        if "HTTP 404" in str(exc):
            return default
        raise
    return payload.get("value", default)


def save_app_setting(settings: dict, setting_key: str, value: object) -> object:
    payload = request_json(
        settings,
        "POST",
        "/api/app-setting",
        {"key": str(setting_key or "").strip(), "value": value},
    )
    return payload.get("value", value)


def fetch_tech_pack_records(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/tech-pack/records")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_tech_pack_records(settings: dict, rows: list[dict]) -> list[dict]:
    payload = request_json(settings, "POST", "/api/tech-pack/records", {"items": list(rows)})
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_tech_pack_record(settings: dict, row: dict) -> dict:
    payload = request_json(settings, "POST", "/api/tech-pack/record", {"item": dict(row or {})})
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def save_tech_pack_record_patch(settings: dict, style_code: str, changes: dict) -> dict:
    payload = request_json(
        settings,
        "POST",
        "/api/tech-pack/record-patch",
        {"style_code": str(style_code or "").strip(), "changes": dict(changes or {})},
    )
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def fetch_bom_items(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/bom/items")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_bom_item(settings: dict, row: dict) -> dict:
    payload = request_json(settings, "POST", "/api/bom/item", {"item": dict(row or {})})
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def delete_bom_item(settings: dict, item_id: str) -> dict:
    return request_json(settings, "POST", "/api/bom/item/delete", {"item_id": str(item_id or "").strip()})


def fetch_sample_records(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/samples/records")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_sample_records(settings: dict, rows: list[dict]) -> list[dict]:
    payload = request_json(settings, "POST", "/api/samples/records", {"items": list(rows)})
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_sample_record(settings: dict, row: dict) -> dict:
    payload = request_json(settings, "POST", "/api/samples/record", {"item": dict(row or {})})
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def fetch_hr_employees(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/hr/employees")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_hr_employees(settings: dict, rows: list[dict]) -> list[dict]:
    payload = request_json(settings, "POST", "/api/hr/employees", {"items": list(rows)})
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_hr_employee(settings: dict, row: dict) -> dict:
    payload = request_json(settings, "POST", "/api/hr/employee", {"item": dict(row or {})})
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def fetch_hr_attendance_records(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/hr/attendance-records")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_hr_attendance_records(settings: dict, rows: list[dict]) -> list[dict]:
    payload = request_json(settings, "POST", "/api/hr/attendance-records", {"items": list(rows)})
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def save_hr_attendance_record(settings: dict, row: dict) -> dict:
    payload = request_json(settings, "POST", "/api/hr/attendance-record", {"item": dict(row or {})})
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def hr_attendance_check_in(settings: dict, employee: dict, *, actor_email: str = "") -> dict:
    payload = request_json(
        settings,
        "POST",
        "/api/hr/attendance/check-in",
        {"employee": dict(employee or {}), "actor_email": str(actor_email or "").strip()},
    )
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def hr_attendance_check_out(settings: dict, employee: dict, *, actor_email: str = "") -> dict:
    payload = request_json(
        settings,
        "POST",
        "/api/hr/attendance/check-out",
        {"employee": dict(employee or {}), "actor_email": str(actor_email or "").strip()},
    )
    item = payload.get("item")
    return dict(item) if isinstance(item, dict) else {}


def fetch_tech_pack_hidden_styles(settings: dict) -> list[str]:
    payload = request_json(settings, "GET", "/api/tech-pack/hidden-styles")
    items = payload.get("items", [])
    return [str(item).strip() for item in items if str(item).strip()]


def save_tech_pack_hidden_styles(settings: dict, style_codes: list[object]) -> list[str]:
    payload = request_json(settings, "POST", "/api/tech-pack/hidden-styles", {"items": list(style_codes)})
    items = payload.get("items", [])
    return [str(item).strip() for item in items if str(item).strip()]


def fetch_order_detail(settings: dict, order_id: int) -> dict | None:
    try:
        payload = request_json(settings, "GET", f"/api/orders/{int(order_id)}")
    except RuntimeError as exc:
        if "HTTP 404" in str(exc):
            return None
        raise
    return payload if isinstance(payload, dict) else None


def import_purchase_order(settings: dict, purchase_order: dict) -> dict:
    return request_json(settings, "POST", "/api/orders/import", {"order": purchase_order})


def delete_purchase_order(settings: dict, order_id: int) -> dict:
    return request_json(settings, "POST", f"/api/orders/{int(order_id)}/delete")


def fetch_costing_imports(settings: dict) -> list[dict]:
    payload = request_json(settings, "GET", "/api/costing/imports")
    items = payload.get("items", [])
    return [dict(item) for item in items if isinstance(item, dict)]


def import_costing(settings: dict, costing_payload: dict) -> dict:
    return request_json(settings, "POST", "/api/costing/imports", {"costing": costing_payload})


def import_costing_worksheet(settings: dict, costing_payload: dict) -> dict:
    return request_json(settings, "POST", "/api/costing/ws-imports", {"costing": costing_payload})


def update_costing_worksheet_row(
    settings: dict,
    *,
    source_workbook: str,
    source_sheet: str,
    source_row_number: int,
    updates: dict,
) -> dict:
    return request_json(
        settings,
        "POST",
        "/api/costing/ws-rows/update",
        {
            "source_workbook": str(source_workbook or ""),
            "source_sheet": str(source_sheet or ""),
            "source_row_number": int(source_row_number or 0),
            "updates": dict(updates or {}),
        },
    )


def delete_costing_styles(settings: dict, style_keys: list[object]) -> dict:
    normalized = [
        item
        if isinstance(item, dict)
        else {
            "collection": str(item[0] or ""),
            "season": str(item[1] or ""),
            "style_code": str(item[2] or ""),
        }
        for item in style_keys
    ]
    return request_json(settings, "POST", "/api/costing/styles/delete", {"style_keys": normalized})
