"""Complex domain-specific overrides that cannot be expressed declaratively.

These functions are called by generated api/*.py classes for methods marked
with "complex_override" in convenience.json. Each function takes the API
instance as first argument and delegates to base class methods.
"""

from __future__ import annotations

import mimetypes
from pathlib import Path
from typing import Any, Literal, overload

from pydantic import Field, StrictStr

from goodmem._api_base.memories import AsyncMemoriesAPIBase, MemoriesAPIBase
from goodmem.models.memory import Memory
from goodmem.models.memory_creation_request import (
    MemoryCreationRequest as _GeneratedMemoryCreationRequest,
)
from goodmem.models.batch_memory_response import BatchMemoryResponse
from goodmem.models.retrieve_memory_event import RetrieveMemoryEvent
from goodmem.models.retrieve_memory_request import RetrieveMemoryRequest
from goodmem.streaming import RetrieveMemoryStream, AsyncRetrieveMemoryStream
from goodmem._transforms import apply_convenience_transforms


# ---------------------------------------------------------------------------
# Memory creation with file upload
# ---------------------------------------------------------------------------

class MemoryCreationRequest(_GeneratedMemoryCreationRequest):
    """Convenience version — ``content_type`` is optional (auto-inferred for text).

    Identical to the generated ``MemoryCreationRequest`` except ``content_type``
    defaults to ``None``.  When passed to ``batch_create``, the SDK infers
    ``"text/plain"`` when ``original_content`` is a string and no explicit
    ``content_type`` is provided.
    """

    content_type: StrictStr | None = Field(  # type: ignore[assignment]
        default=None,
        description="MIME type of the content. Auto-inferred as 'text/plain' when original_content is a string.",
        alias="contentType",
    )


def _infer_content_type(file_path: str | Path) -> str:
    """Infer MIME type from file extension."""
    mime_type, _ = mimetypes.guess_type(str(file_path))
    return mime_type or "application/octet-stream"


def _validate_content_sources(file_path: str | None, kwargs: dict[str, Any]) -> None:
    """Validate mutually exclusive content sources."""
    sources = []
    if file_path is not None:
        sources.append("file_path")
    if "original_content" in kwargs:
        sources.append("original_content")
    if "original_content_b64" in kwargs:
        sources.append("original_content_b64")
    if len(sources) > 1:
        raise ValueError(
            f"Content must be provided via exactly one of file_path, "
            f"original_content, or original_content_b64. "
            f"Got: {', '.join(sources)}"
        )


def _create_with_file_sync(api: MemoriesAPIBase, file_path: str, kwargs: dict) -> Memory:
    """Handle multipart file upload (sync)."""
    from goodmem._base import _op_meta, _prepare_create_payload
    from goodmem.errors import raise_for_status

    path = Path(file_path)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {path}")
    kwargs.setdefault("content_type", _infer_content_type(path))
    request_data = api._create_model.model_validate(kwargs)
    with open(path, "rb") as f:
        # Pack request + file into kwargs and let _prepare_create_payload
        # handle the multipart dispatch.
        create_kwargs = {
            "request": request_data,
            "file": (path.name, f, kwargs["content_type"]),
        }
        op = _op_meta(api, "create")
        json_payload, data_payload, files_payload = _prepare_create_payload(
            create_model=api._create_model, op=op, kwargs=create_kwargs,
        )
        resp = api._http.post(
            "/v1/memories",
            json=json_payload, data=data_payload, files=files_payload,
            headers=api._headers,
        )
        raise_for_status(resp)
        return Memory.model_validate(resp.json())


async def _create_with_file_async(api: AsyncMemoriesAPIBase, file_path: str, kwargs: dict) -> Memory:
    """Handle multipart file upload (async)."""
    from goodmem._base import _op_meta, _prepare_create_payload
    from goodmem.errors import raise_for_status

    path = Path(file_path)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {path}")
    kwargs.setdefault("content_type", _infer_content_type(path))
    request_data = api._create_model.model_validate(kwargs)
    with open(path, "rb") as f:
        create_kwargs = {
            "request": request_data,
            "file": (path.name, f, kwargs["content_type"]),
        }
        op = _op_meta(api, "create")
        json_payload, data_payload, files_payload = _prepare_create_payload(
            create_model=api._create_model, op=op, kwargs=create_kwargs,
        )
        resp = await api._http.post(
            "/v1/memories",
            json=json_payload, data=data_payload, files=files_payload,
            headers=api._headers,
        )
        raise_for_status(resp)
        return Memory.model_validate(resp.json())


def memories_create_sync(
    api: MemoriesAPIBase, *, file_path: str | None = None, **kwargs: Any,
) -> Memory:
    _validate_content_sources(file_path, kwargs)
    if file_path is not None:
        return _create_with_file_sync(api, file_path, kwargs)
    if "content_type" not in kwargs and "original_content" in kwargs:
        if isinstance(kwargs["original_content"], str):
            kwargs["content_type"] = "text/plain"
    return MemoriesAPIBase.create(api, **kwargs)


async def memories_create_async(
    api: AsyncMemoriesAPIBase, *, file_path: str | None = None, **kwargs: Any,
) -> Memory:
    _validate_content_sources(file_path, kwargs)
    if file_path is not None:
        return await _create_with_file_async(api, file_path, kwargs)
    if "content_type" not in kwargs and "original_content" in kwargs:
        if isinstance(kwargs["original_content"], str):
            kwargs["content_type"] = "text/plain"
    return await AsyncMemoriesAPIBase.create(api, **kwargs)


# ---------------------------------------------------------------------------
# Memory retrieval with convenience transforms
# ---------------------------------------------------------------------------

def _build_retrieve_body(message: str, kwargs: dict[str, Any]) -> dict:
    """Build RetrieveMemoryRequest dict, handling convenience params."""
    kwargs.pop("accept", None)  # header param, not a body field
    apply_convenience_transforms(("memories", "retrieve"), kwargs)
    kwargs["message"] = message
    body = RetrieveMemoryRequest.model_validate(kwargs)
    return body.model_dump(by_alias=True, exclude_unset=True)


def memories_retrieve_sync(
    api: MemoriesAPIBase, *, message: str, stream: bool = True, **kwargs: Any,
) -> RetrieveMemoryStream | list[RetrieveMemoryEvent]:
    body_dict = _build_retrieve_body(message, kwargs)
    return api._retrieve_with_body(body_dict, stream=stream)


async def memories_retrieve_async(
    api: AsyncMemoriesAPIBase, *, message: str, stream: bool = True, **kwargs: Any,
) -> AsyncRetrieveMemoryStream | list[RetrieveMemoryEvent]:
    body_dict = _build_retrieve_body(message, kwargs)
    return await api._retrieve_with_body(body_dict, stream=stream)


# ---------------------------------------------------------------------------
# Batch create with content_type inference
# ---------------------------------------------------------------------------

def memories_batch_create_sync(
    api: MemoriesAPIBase, *, requests: list[MemoryCreationRequest],
) -> BatchMemoryResponse:
    processed = []
    for req in requests:
        d = req.model_dump(by_alias=False, exclude_unset=True)
        if req.content_type is None and req.original_content is not None:
            if isinstance(req.original_content, str):
                d["content_type"] = "text/plain"
        processed.append(d)
    return MemoriesAPIBase.batch_create(api, requests=processed)


async def memories_batch_create_async(
    api: AsyncMemoriesAPIBase, *, requests: list[MemoryCreationRequest],
) -> BatchMemoryResponse:
    processed = []
    for req in requests:
        d = req.model_dump(by_alias=False, exclude_unset=True)
        if req.content_type is None and req.original_content is not None:
            if isinstance(req.original_content, str):
                d["content_type"] = "text/plain"
        processed.append(d)
    return await AsyncMemoriesAPIBase.batch_create(api, requests=processed)
