"""Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
Resumable file download helper using dedicated transfer HTTP routes.
"""

from __future__ import annotations

import os
from typing import Callable
from typing import Optional

from os.path import exists
from os.path import getsize

from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
    TransferChecksumMismatchClientError,
)
from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
    TransferRangeNotSatisfiableClientError,
)
from mcp_proxy_adapter.client.jsonrpc_client.transport import JsonRpcTransport
from mcp_proxy_adapter.transfer.checksums import verify_file_checksum
from mcp_proxy_adapter.transfer.compression import decompress_file
from mcp_proxy_adapter.transfer.errors import TransferChecksumMismatchError
from mcp_proxy_adapter.transfer.models import TransferDownloadReceipt

CHECKSUM_SHA256 = "sha256"
PARTIAL_SUFFIX = ".partial"


def _coerce_json_int(value: object, field: str) -> int:
    if isinstance(value, bool):
        raise ValueError(field)
    if isinstance(value, int):
        return value
    if isinstance(value, str):
        try:
            return int(value, 10)
        except ValueError:
            raise ValueError(field) from None
    raise ValueError(field)


def _require_str(data: dict[str, object], key: str) -> str:
    if key not in data:
        raise ValueError(key)
    val = data[key]
    if not isinstance(val, str):
        raise ValueError(key)
    return val


def _status_completed_from_session(data: dict[str, object]) -> bool:
    if "completed" in data:
        raw_completed = data["completed"]
        if isinstance(raw_completed, bool):
            return raw_completed
        return bool(raw_completed)
    st = data.get("status")
    if isinstance(st, str) and st in ("completed", "uploaded"):
        return True
    return False


def _download_receipt_from_dict(
    data: dict[str, object],
    *,
    destination_path: str,
    checksum_verified: bool,
    completed: bool,
    final_offset: int,
) -> TransferDownloadReceipt:
    from_dict = getattr(TransferDownloadReceipt, "from_dict", None)
    if callable(from_dict):
        merged: dict[str, object] = dict(data)
        merged["destination_path"] = destination_path
        merged["checksum_verified"] = checksum_verified
        merged["completed"] = completed
        merged["offset"] = final_offset
        result = from_dict(merged)
        if isinstance(result, TransferDownloadReceipt):
            return result

    transfer_id = _require_str(data, "transfer_id")
    size_bytes = _coerce_json_int(data["size_bytes"], "size_bytes")
    checksum_algorithm = _require_str(data, "checksum_algorithm")
    checksum_value = _require_str(data, "checksum_value")
    compression = _require_str(data, "compression")
    status = _require_str(data, "status")

    return TransferDownloadReceipt(
        transfer_id=transfer_id,
        destination_path=destination_path,
        offset=final_offset,
        size_bytes=size_bytes,
        checksum_algorithm=checksum_algorithm,
        checksum_value=checksum_value,
        compression=compression,
        status=status,
        completed=completed,
        checksum_verified=checksum_verified,
    )


class TransferDownloader:
    """Orchestrates resumable download via JsonRpcTransport transfer endpoints."""

    def __init__(
        self,
        transport: JsonRpcTransport,
        default_chunk_size: int,
        checksum_algorithm: str = "sha256",
    ) -> None:
        """Store transport, default chunk size, and checksum algorithm."""
        self._transport = transport
        if default_chunk_size < 1:
            raise ValueError("default_chunk_size must be >= 1")
        self._default_chunk_size = default_chunk_size
        if checksum_algorithm != CHECKSUM_SHA256:
            raise ValueError("only sha256 is supported")
        self._checksum_algorithm = checksum_algorithm

    async def download_file(
        self,
        transfer_id: str,
        destination_path: str,
        chunk_size: int | None = None,
        on_progress: Optional[Callable[[int, int], None]] = None,
    ) -> TransferDownloadReceipt:
        """Download from offset 0 into a temp file, finalize, verify checksum, return receipt."""
        effective_chunk = (
            chunk_size if chunk_size is not None else self._default_chunk_size
        )
        if effective_chunk < 1:
            raise ValueError("chunk_size must be >= 1")

        status = await self._transport.get_download_session(transfer_id)
        size_bytes = _coerce_json_int(status["size_bytes"], "size_bytes")
        compression = _require_str(status, "compression")
        checksum_value = _require_str(status, "checksum_value")

        work_path = destination_path + PARTIAL_SUFFIX
        with open(work_path, "wb"):
            pass

        await self._download_chunks(
            transfer_id=transfer_id,
            work_path=work_path,
            start_offset=0,
            effective_chunk=effective_chunk,
            size_bytes=size_bytes,
            session_status=status,
            on_progress=on_progress,
        )

        return self._finalize_verify_and_receipt(
            work_path=work_path,
            destination_path=destination_path,
            compression=compression,
            checksum_value=checksum_value,
            transfer_id=transfer_id,
            status=status,
            size_bytes=size_bytes,
        )

    async def resume_download(
        self,
        transfer_id: str,
        destination_path: str,
        chunk_size: int | None = None,
    ) -> TransferDownloadReceipt:
        """Resume using local partial file size as offset; then same as download_file."""
        work_path = destination_path + PARTIAL_SUFFIX
        if exists(work_path):
            offset = getsize(work_path)
        else:
            offset = 0
            with open(work_path, "wb"):
                pass

        effective_chunk = (
            chunk_size if chunk_size is not None else self._default_chunk_size
        )
        if effective_chunk < 1:
            raise ValueError("chunk_size must be >= 1")

        status = await self._transport.get_download_session(transfer_id)
        size_bytes = _coerce_json_int(status["size_bytes"], "size_bytes")
        compression = _require_str(status, "compression")
        checksum_value = _require_str(status, "checksum_value")

        if offset > size_bytes:
            raise TransferRangeNotSatisfiableClientError(
                "local partial larger than server file"
            )

        await self._download_chunks(
            transfer_id=transfer_id,
            work_path=work_path,
            start_offset=offset,
            effective_chunk=effective_chunk,
            size_bytes=size_bytes,
            session_status=status,
            on_progress=None,
        )

        return self._finalize_verify_and_receipt(
            work_path=work_path,
            destination_path=destination_path,
            compression=compression,
            checksum_value=checksum_value,
            transfer_id=transfer_id,
            status=status,
            size_bytes=size_bytes,
        )

    async def _download_chunks(
        self,
        *,
        transfer_id: str,
        work_path: str,
        start_offset: int,
        effective_chunk: int,
        size_bytes: int,
        session_status: dict[str, object],
        on_progress: Optional[Callable[[int, int], None]],
    ) -> None:
        offset = start_offset
        if offset >= size_bytes and _status_completed_from_session(session_status):
            return
        while True:
            chunk_bytes, meta = await self._transport.download_chunk(
                transfer_id, offset, effective_chunk
            )
            completed = bool(meta["completed"])
            next_offset = _coerce_json_int(meta["offset"], "offset")
            with open(work_path, "ab") as out:
                out.write(chunk_bytes)
            offset = next_offset
            if on_progress is not None:
                on_progress(offset, size_bytes)
            if completed:
                break

    def _verify_download_checksum(
        self, verify_path: str, checksum_value: str, transfer_id: str
    ) -> None:
        try:
            verify_file_checksum(
                verify_path,
                checksum_value,
                algorithm=self._checksum_algorithm,
                transfer_id=transfer_id,
                phase="download",
            )
        except TransferChecksumMismatchError as exc:
            raise TransferChecksumMismatchClientError(str(exc)) from exc

    def _finalize_verify_and_receipt(
        self,
        *,
        work_path: str,
        destination_path: str,
        compression: str,
        checksum_value: str,
        transfer_id: str,
        status: dict[str, object],
        size_bytes: int,
    ) -> TransferDownloadReceipt:
        if compression == "identity":
            self._verify_download_checksum(work_path, checksum_value, transfer_id)
            os.replace(work_path, destination_path)
        elif compression == "gzip":
            decompress_file(work_path, destination_path, algorithm="gzip")
            os.remove(work_path)
            self._verify_download_checksum(
                destination_path, checksum_value, transfer_id
            )
        else:
            raise ValueError("compression must be identity or gzip")

        return _download_receipt_from_dict(
            status,
            destination_path=destination_path,
            checksum_verified=True,
            completed=True,
            final_offset=size_bytes,
        )


__all__ = [
    "CHECKSUM_SHA256",
    "PARTIAL_SUFFIX",
    "TransferDownloader",
]
