"""
Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com

Shared transfer session lifecycle logic for REST handlers and JSON-RPC commands.
"""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from mcp_proxy_adapter.transfer.server_store import TransferServerStore

SHA256_HEX_LEN = 64
CHECKSUM_HEX_RE = re.compile(rf"^[0-9a-f]{{{SHA256_HEX_LEN}}}$")


class TransferPayloadValidationError(Exception):
    """Invalid transfer request payload (REST maps to 422 InvalidRequest)."""

    def __init__(self, missing_fields: list[str]) -> None:
        self.missing_fields = list(missing_fields)
        super().__init__("Request validation failed")


class TransferSessionKindMismatch(Exception):
    """Session exists but direction does not match the requested operation."""

    def __init__(self, *, transfer_id: str, expected: str) -> None:
        self.transfer_id = transfer_id
        self.expected = expected
        super().__init__(transfer_id)


def _get_store() -> "TransferServerStore":
    from mcp_proxy_adapter.api.handlers import get_transfer_store

    return get_transfer_store()


def _merge_cfg() -> dict[str, object]:
    from mcp_proxy_adapter.api.handlers import _merge_transfer_config

    return _merge_transfer_config()


def build_transfer_chunk_transport(
    *, direction: str, transfer_id: str
) -> dict[str, object]:
    """
    Build chunk URL template and method hints for JSON-RPC clients (spec §7).

    Args:
        direction: ``upload`` or ``download``.
        transfer_id: Session id (reserved for future absolute URL helpers).

    Returns:
        Dict with chunk_method, chunk_path_template, protocol_hint.
    """
    _ = transfer_id
    if direction == "upload":
        return {
            "chunk_method": "PUT",
            "chunk_path_template": (
                "/api/transfer/uploads/{transfer_id}/chunks?offset={offset}"
            ),
            "protocol_hint": "same_as_jsonrpc",
        }
    if direction == "download":
        return {
            "chunk_method": "GET",
            "chunk_path_template": (
                "/api/transfer/downloads/{transfer_id}/chunks?"
                "offset={offset}&limit={limit}"
            ),
            "protocol_hint": "same_as_jsonrpc",
        }
    raise ValueError(f"unknown direction: {direction}")


async def run_create_upload_session(payload: dict[str, object]) -> dict[str, object]:
    required = ("filename", "size_bytes", "checksum_value", "compression")
    missing = [k for k in required if k not in payload]
    if missing:
        raise TransferPayloadValidationError(missing)
    filename = str(payload["filename"]).strip()
    if not filename:
        raise TransferPayloadValidationError(["filename"])
    size_bytes = payload["size_bytes"]
    if not isinstance(size_bytes, int) or size_bytes < 0:
        raise TransferPayloadValidationError(["size_bytes"])
    checksum_value = str(payload["checksum_value"]).strip()
    if not CHECKSUM_HEX_RE.fullmatch(checksum_value):
        raise TransferPayloadValidationError(["checksum_value"])
    compression = str(payload["compression"])
    if compression not in ("identity", "gzip"):
        raise TransferPayloadValidationError(["compression"])
    cfg = _merge_cfg()
    checksum_algorithm = str(
        payload.get("checksum_algorithm") or cfg["checksum_algorithm"]
    )
    if checksum_algorithm != "sha256":
        raise TransferPayloadValidationError(["checksum_algorithm"])
    chunk_size: int | None = None
    if "chunk_size" in payload and payload["chunk_size"] is not None:
        if not isinstance(payload["chunk_size"], int):
            raise TransferPayloadValidationError(["chunk_size"])
        chunk_size = int(payload["chunk_size"])

    def _corr(key: str) -> str | None:
        if key not in payload or payload[key] is None:
            return None
        if not isinstance(payload[key], str):
            raise TransferPayloadValidationError([key])
        s = str(payload[key]).strip()
        return s or None

    receipt = _get_store().create_upload_session(
        filename=filename,
        size_bytes=size_bytes,
        checksum_algorithm=checksum_algorithm,
        checksum_value=checksum_value,
        compression=compression,
        chunk_size=chunk_size,
        job_id=_corr("job_id"),
        correlation_id=_corr("correlation_id"),
        command=_corr("command"),
        inbound_transfer_id=_corr("inbound_transfer_id"),
    )
    return receipt.to_dict()


async def run_get_upload_session(transfer_id: str) -> dict[str, object]:
    session = _get_store().get_session(transfer_id)
    if str(session.direction) != "upload":
        raise TransferSessionKindMismatch(transfer_id=transfer_id, expected="upload")
    return session.to_dict()


async def run_complete_upload_session(transfer_id: str) -> dict[str, object]:
    """
    Finalize upload (shared by REST and JSON-RPC). Emits transfer WS events on success/failure.
    """
    from mcp_proxy_adapter.core.job_push.events import (
        EVENT_TRANSFER_COMPLETED,
        EVENT_TRANSFER_EXPIRED,
        EVENT_TRANSFER_FAILED,
    )
    from mcp_proxy_adapter.core.job_push.transfer_push import (
        build_transfer_expired_ws_payload,
        notify_transfer_state_changed,
    )
    from mcp_proxy_adapter.transfer import TransferChecksumMismatchError
    from mcp_proxy_adapter.transfer import TransferCompressionError
    from mcp_proxy_adapter.transfer import TransferError
    from mcp_proxy_adapter.transfer import TransferExpiredError
    from mcp_proxy_adapter.transfer.progress_percent import (
        compute_transfer_progress_percent,
    )

    tid = str(transfer_id).strip()
    try:
        session = _get_store().get_session(tid)
        if str(session.direction) != "upload":
            raise TransferSessionKindMismatch(transfer_id=tid, expected="upload")
    except TransferExpiredError as exc:
        eid = str(exc.transfer_id or tid).strip()
        if eid:
            await notify_transfer_state_changed(
                eid,
                EVENT_TRANSFER_EXPIRED,
                build_transfer_expired_ws_payload(
                    transfer_id=eid,
                    direction="upload",
                    message=str(exc),
                ),
            )
        raise
    try:
        receipt = _get_store().complete_upload_session(tid)
    except TransferExpiredError as exc:
        eid = str(exc.transfer_id or tid).strip()
        if eid:
            await notify_transfer_state_changed(
                eid,
                EVENT_TRANSFER_EXPIRED,
                build_transfer_expired_ws_payload(
                    transfer_id=eid,
                    direction="upload",
                    message=str(exc),
                ),
            )
        raise
    except (
        TransferChecksumMismatchError,
        TransferCompressionError,
        TransferError,
    ) as exc:
        err_tid = str(getattr(exc, "transfer_id", None) or tid).strip()
        pct = 0
        if err_tid:
            try:
                s = _get_store().get_session(err_tid)
                pct = compute_transfer_progress_percent(
                    int(s.offset), int(s.size_bytes), terminal=False
                )
            except Exception:
                pct = 0
        payload: dict[str, object] = {
            "direction": "upload",
            "error_type": type(exc).__name__,
            "message": str(exc),
            "progress_percent": pct,
        }
        if isinstance(exc, TransferChecksumMismatchError):
            payload["error_type"] = "TransferChecksumMismatchError"
            payload["checksum_algorithm"] = "sha256"
            if exc.checksum_expected is not None:
                payload["checksum_value"] = str(exc.checksum_expected)
        if err_tid:
            await notify_transfer_state_changed(err_tid, EVENT_TRANSFER_FAILED, payload)
        raise

    data = receipt.to_dict()
    await notify_transfer_state_changed(
        tid,
        EVENT_TRANSFER_COMPLETED,
        {
            "direction": "upload",
            "status": str(data["status"]),
            "filename": str(data["filename"]),
            "checksum_algorithm": str(data["checksum_algorithm"]),
            "checksum_value": str(data["checksum_value"]),
            "checksum_verified": True,
            "progress_percent": 100,
        },
    )
    return data


async def run_create_download_session(payload: dict[str, object]) -> dict[str, object]:
    required = ("source_path", "filename", "compression")
    missing = [k for k in required if k not in payload]
    if missing:
        raise TransferPayloadValidationError(missing)
    source_path = str(payload["source_path"])
    filename = str(payload["filename"]).strip()
    if not filename:
        raise TransferPayloadValidationError(["filename"])
    compression = str(payload["compression"])
    if compression not in ("identity", "gzip"):
        raise TransferPayloadValidationError(["compression"])
    cfg = _merge_cfg()
    checksum_algorithm = str(cfg["checksum_algorithm"])
    if checksum_algorithm != "sha256":
        raise TransferPayloadValidationError(["checksum_algorithm"])
    chunk_size: int | None = None
    if "chunk_size" in payload and payload["chunk_size"] is not None:
        if not isinstance(payload["chunk_size"], int):
            raise TransferPayloadValidationError(["chunk_size"])
        chunk_size = int(payload["chunk_size"])

    def _corr_dl(key: str) -> str | None:
        if key not in payload or payload[key] is None:
            return None
        if not isinstance(payload[key], str):
            raise TransferPayloadValidationError([key])
        s = str(payload[key]).strip()
        return s or None

    delete_on_ack = bool(payload.get("delete_source_on_ack", False))
    receipt = _get_store().create_download_session(
        source_path=source_path,
        filename=filename,
        checksum_algorithm=checksum_algorithm,
        compression=compression,
        chunk_size=chunk_size,
        job_id=_corr_dl("job_id"),
        correlation_id=_corr_dl("correlation_id"),
        command=_corr_dl("command"),
        inbound_transfer_id=_corr_dl("inbound_transfer_id"),
        delete_source_on_ack=delete_on_ack,
    )
    data = receipt.to_dict()
    tid = str(data.get("transfer_id", "")).strip()
    if tid:
        session = _get_store().try_load_session_metadata(tid)
        if session is not None and session.job_id:
            from mcp_proxy_adapter.core.job_push.events import EVENT_ARTIFACT_READY
            from mcp_proxy_adapter.core.job_push.queue_custom_push import (
                push_queue_job_ws_payload,
            )

            sz = session.plaintext_size_bytes
            if sz is None:
                sz = int(session.size_bytes)
            body: dict[str, object] = {
                "event": EVENT_ARTIFACT_READY,
                "job_id": session.job_id,
                "outbound_transfer_id": tid,
                "filename": str(session.filename),
                "checksum_algorithm": str(session.checksum_algorithm),
                "checksum_value": str(session.checksum_value),
                "size_bytes": int(sz),
            }
            if session.correlation_id:
                body["correlation_id"] = session.correlation_id
            await push_queue_job_ws_payload(body)
    return data


async def run_get_download_session(transfer_id: str) -> dict[str, object]:
    session = _get_store().get_session(transfer_id)
    if str(session.direction) != "download":
        raise TransferSessionKindMismatch(transfer_id=transfer_id, expected="download")
    return session.to_dict()
