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

from __future__ import annotations

from typing import Callable
from typing import Optional

from os.path import basename
from os.path import getsize

from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
    TransferResumeConflictClientError,
)
from mcp_proxy_adapter.client.jsonrpc_client.transport import JsonRpcTransport
from mcp_proxy_adapter.transfer.checksums import compute_file_checksum
from mcp_proxy_adapter.transfer.models import TransferReceipt

DEFAULT_COMPRESSION_IDENTITY = "identity"
CHECKSUM_SHA256 = "sha256"


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 _receipt_from_response(data: dict[str, object]) -> TransferReceipt:
    from_dict = getattr(TransferReceipt, "from_dict", None)
    if callable(from_dict):
        result = from_dict(data)
        if isinstance(result, TransferReceipt):
            return result

    def _require_str(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 _require_int(key: str) -> int:
        if key not in data:
            raise ValueError(key)
        return _coerce_json_int(data[key], key)

    transfer_id = _require_str("transfer_id")
    filename = _require_str("filename")
    size_bytes = _require_int("size_bytes")
    checksum_algorithm = _require_str("checksum_algorithm")
    checksum_value = _require_str("checksum_value")
    compression = _require_str("compression")
    chunk_size = _require_int("chunk_size")
    offset = _require_int("offset")
    status = _require_str("status")

    if "completed" in data:
        raw_completed = data["completed"]
        if isinstance(raw_completed, bool):
            completed = raw_completed
        else:
            completed = bool(raw_completed)
    else:
        completed = status in ("uploaded", "completed")

    expires_at: str | None
    if "expires_at" not in data or data["expires_at"] is None:
        expires_at = None
    else:
        ev = data["expires_at"]
        expires_at = ev if isinstance(ev, str) else str(ev)

    return TransferReceipt(
        transfer_id=transfer_id,
        filename=filename,
        size_bytes=size_bytes,
        checksum_algorithm=checksum_algorithm,
        checksum_value=checksum_value,
        compression=compression,
        chunk_size=chunk_size,
        offset=offset,
        status=status,
        completed=completed,
        expires_at=expires_at,
    )


class TransferUploader:
    """Orchestrates resumable upload via JsonRpcTransport transfer endpoints."""

    def __init__(
        self,
        transport: JsonRpcTransport,
        default_chunk_size: int,
        checksum_algorithm: str = "sha256",
        compression: str = "identity",
    ) -> None:
        """Store transport, default chunk size, checksum algorithm, and default compression."""
        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
        if compression not in (DEFAULT_COMPRESSION_IDENTITY, "gzip"):
            raise ValueError("compression must be identity or gzip")
        self._compression_default = compression

    async def upload_file(
        self,
        source_path: str,
        filename: str | None = None,
        compression: str | None = None,
        chunk_size: int | None = None,
        on_progress: Optional[Callable[[int, int], None]] = None,
    ) -> TransferReceipt:
        """Create session, upload all chunks, complete session, return final receipt."""
        comp = compression if compression is not None else self._compression_default
        if comp not in (DEFAULT_COMPRESSION_IDENTITY, "gzip"):
            raise ValueError("compression must be identity or gzip")
        comp_norm = comp

        name = filename if filename is not None else basename(source_path)
        size_bytes = getsize(source_path)
        checksum_value = compute_file_checksum(
            source_path,
            algorithm=self._checksum_algorithm,
            chunk_size=self._default_chunk_size,
        )

        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")

        session = await self._transport.create_upload_session(
            name,
            size_bytes,
            self._checksum_algorithm,
            checksum_value,
            comp_norm,
            chunk_size=effective_chunk,
        )
        transfer_id = str(session["transfer_id"])
        offset = _coerce_json_int(session.get("offset", 0), "offset")

        with open(source_path, "rb") as f:
            f.seek(offset)
            while offset < size_bytes:
                to_read = min(effective_chunk, size_bytes - offset)
                chunk = f.read(to_read)
                if len(chunk) != to_read:
                    raise ValueError("unexpected EOF while reading upload source")
                receipt_dict = await self._transport.upload_chunk(
                    transfer_id, offset, chunk
                )
                offset = _coerce_json_int(receipt_dict["offset"], "offset")
                if on_progress is not None:
                    on_progress(offset, size_bytes)

        final = await self._transport.complete_upload_session(transfer_id)
        return _receipt_from_response(final)

    async def resume_upload(
        self,
        source_path: str,
        transfer_id: str,
        chunk_size: int | None = None,
    ) -> TransferReceipt:
        """Resume from server-reported offset, upload remainder, complete, return receipt."""
        status = await self._transport.get_upload_session(transfer_id)
        server_offset = _coerce_json_int(status["offset"], "offset")
        size_bytes = getsize(source_path)
        if server_offset > size_bytes:
            raise TransferResumeConflictClientError(
                "local file smaller than server offset"
            )

        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")

        offset = server_offset
        with open(source_path, "rb") as f:
            f.seek(offset)
            while offset < size_bytes:
                to_read = min(effective_chunk, size_bytes - offset)
                chunk = f.read(to_read)
                if len(chunk) != to_read:
                    raise ValueError("unexpected EOF while reading upload source")
                receipt_dict = await self._transport.upload_chunk(
                    transfer_id, offset, chunk
                )
                offset = _coerce_json_int(receipt_dict["offset"], "offset")

        final = await self._transport.complete_upload_session(transfer_id)
        return _receipt_from_response(final)


__all__ = [
    "CHECKSUM_SHA256",
    "DEFAULT_COMPRESSION_IDENTITY",
    "TransferUploader",
]
