"""Shared multimodal serialization helpers for provider backends."""

from __future__ import annotations

import base64
import json
import mimetypes
from pathlib import Path
from typing import Any

from logos_adk.types import ContentPart, Message, ToolCall, content_text, normalize_content_parts


def part_to_data_url(part: ContentPart) -> str | None:
    """Resolve a content part to a data URL when possible."""
    if part.data and part.mime_type:
        return f"data:{part.mime_type};base64,{part.data}"
    if not part.uri:
        return None
    if part.uri.startswith("data:"):
        return part.uri
    candidate = Path(part.uri)
    if candidate.exists() and candidate.is_file():
        mime_type = part.mime_type or mimetypes.guess_type(candidate.name)[0] or "application/octet-stream"
        encoded = base64.b64encode(candidate.read_bytes()).decode("ascii")
        return f"data:{mime_type};base64,{encoded}"
    return None


def part_inline_bytes(part: ContentPart) -> tuple[str, str] | None:
    """Return (mime_type, base64_data) for inline media payloads."""
    if part.data and part.mime_type:
        return part.mime_type, part.data
    if not part.uri:
        return None
    candidate = Path(part.uri)
    if candidate.exists() and candidate.is_file():
        mime_type = part.mime_type or mimetypes.guess_type(candidate.name)[0] or "application/octet-stream"
        encoded = base64.b64encode(candidate.read_bytes()).decode("ascii")
        return mime_type, encoded
    if part.uri.startswith("data:") and ";base64," in part.uri:
        prefix, encoded = part.uri.split(";base64,", 1)
        mime_type = prefix.removeprefix("data:") or part.mime_type or "application/octet-stream"
        return mime_type, encoded
    return None


def part_remote_uri(part: ContentPart) -> str | None:
    """Return a remote URI if the part already points at hosted media."""
    if isinstance(part.uri, str) and part.uri.startswith(("http://", "https://", "gs://", "file://")):
        return part.uri
    return None


def openai_responses_input_part(part: ContentPart) -> dict[str, Any]:
    """Convert a content part to OpenAI Responses-style input."""
    if part.type == "text":
        return {"type": "input_text", "text": part.text or ""}
    if part.type == "image":
        image_url = part_remote_uri(part) or part_to_data_url(part)
        if image_url is None:
            raise ValueError("image part requires uri or inline data")
        payload: dict[str, Any] = {"type": "input_image", "image_url": image_url}
        detail = part.metadata.get("detail") if isinstance(part.metadata, dict) else None
        if isinstance(detail, str) and detail:
            payload["detail"] = detail
        return payload
    if part.type == "audio":
        inline = part_inline_bytes(part)
        if inline is not None:
            mime_type, encoded = inline
            fmt = (part.metadata.get("format") if isinstance(part.metadata, dict) else None) or mime_type.split("/")[-1]
            return {"type": "input_audio", "input_audio": {"data": encoded, "format": fmt}}
        audio_url = part_remote_uri(part)
        if audio_url is not None:
            return {"type": "input_audio", "audio_url": audio_url}
        raise ValueError("audio part requires uri or inline data")
    if part.type == "video":
        inline = part_inline_bytes(part)
        if inline is not None:
            mime_type, encoded = inline
            fmt = (part.metadata.get("format") if isinstance(part.metadata, dict) else None) or mime_type.split("/")[-1]
            return {"type": "input_video", "input_video": {"data": encoded, "format": fmt}}
        video_url = part_remote_uri(part)
        if video_url is not None:
            return {"type": "input_video", "video_url": video_url}
        raise ValueError("video part requires uri or inline data")
    if part.type in {"file", "artifact"}:
        file_url = part_remote_uri(part)
        if file_url is not None:
            return {
                "type": "input_file",
                "input_file": {
                    "file_url": file_url,
                    "filename": part.name,
                },
            }
        data_url = part_to_data_url(part)
        if data_url is not None:
            return {
                "type": "input_file",
                "input_file": {
                    "file_data": data_url,
                    "filename": part.name or "attachment",
                },
            }
        raise ValueError("file/artifact part requires uri or inline data")
    raise ValueError(f"Unsupported multimodal part type '{part.type}'")


def openai_responses_message(message: Message) -> dict[str, Any] | list[dict[str, Any]]:
    """Convert a message to OpenAI Responses-style input."""
    if message.role == "tool" and message.tool_result is not None:
        return {
            "type": "function_call_output",
            "call_id": message.tool_result.call_id,
            "output": message.tool_result.error
            or content_text(
                str(message.tool_result.content) if message.tool_result.content is not None else "",
                message.tool_result.content_parts,
            ),
        }
    if message.role == "assistant" and message.tool_calls:
        items: list[dict[str, Any]] = []
        if message.content or message.content_parts:
            parts = normalize_content_parts(message.content_parts)
            if not parts:
                parts = [ContentPart(type="text", text=message.content)]
            elif message.content and not any(part.type == "text" and part.text == message.content for part in parts):
                parts = [ContentPart(type="text", text=message.content)] + parts
            items.append(
                {
                    "role": "assistant",
                    "content": [openai_responses_input_part(part) for part in parts],
                }
            )
        items.extend(
            {
                "type": "function_call",
                "call_id": call.id,
                "name": call.name,
                "arguments": call.arguments,
            }
            for call in message.tool_calls
        )
        return items
    parts = normalize_content_parts(message.content_parts)
    if not parts:
        parts = [ContentPart(type="text", text=message.content)]
    elif message.content and not any(part.type == "text" and part.text == message.content for part in parts):
        parts = [ContentPart(type="text", text=message.content)] + parts
    return {
        "role": message.role,
        "content": [openai_responses_input_part(part) for part in parts],
    }


def parse_openai_responses_output(raw_output: list[Any]) -> tuple[str, list[ContentPart], list[ToolCall]]:
    """Parse OpenAI Responses-style output items."""
    text_parts: list[str] = []
    content_parts: list[ContentPart] = []
    tool_calls: list[ToolCall] = []

    for item in raw_output:
        if not isinstance(item, dict):
            continue
        item_type = item.get("type")
        if item_type == "message":
            for part in item.get("content", []):
                if not isinstance(part, dict):
                    continue
                part_type = part.get("type")
                if part_type in {"output_text", "text"}:
                    text = part.get("text") or ""
                    text_parts.append(text)
                    content_parts.append(ContentPart(type="text", text=text))
                elif part_type in {"output_image", "image"}:
                    content_parts.append(
                        ContentPart(
                            type="image",
                            uri=part.get("image_url"),
                            data=part.get("b64_json"),
                            mime_type=part.get("mime_type") or "image/png",
                            name=part.get("name"),
                            metadata={k: v for k, v in part.items() if k not in {"type", "image_url", "b64_json", "mime_type", "name"}},
                        )
                    )
                elif part_type in {"output_audio", "audio"}:
                    content_parts.append(
                        ContentPart(
                            type="audio",
                            uri=part.get("audio_url"),
                            data=part.get("data"),
                            mime_type=part.get("mime_type"),
                            name=part.get("name"),
                            metadata={k: v for k, v in part.items() if k not in {"type", "audio_url", "data", "mime_type", "name"}},
                        )
                    )
                elif part_type in {"output_video", "video"}:
                    content_parts.append(
                        ContentPart(
                            type="video",
                            uri=part.get("video_url"),
                            data=part.get("data"),
                            mime_type=part.get("mime_type"),
                            name=part.get("name"),
                            metadata={k: v for k, v in part.items() if k not in {"type", "video_url", "data", "mime_type", "name"}},
                        )
                    )
                elif part_type in {"output_file", "file"}:
                    content_parts.append(
                        ContentPart(
                            type="file",
                            uri=part.get("file_url"),
                            data=part.get("data"),
                            mime_type=part.get("mime_type"),
                            name=part.get("filename") or part.get("name"),
                            metadata={k: v for k, v in part.items() if k not in {"type", "file_url", "data", "mime_type", "filename", "name"}},
                        )
                    )
            continue
        if item_type == "function_call":
            arguments = item.get("arguments") or {}
            if isinstance(arguments, str):
                try:
                    arguments = json.loads(arguments)
                except json.JSONDecodeError:
                    arguments = {}
            tool_calls.append(
                ToolCall(
                    id=str(item.get("call_id") or item.get("id") or ""),
                    name=str(item.get("name") or ""),
                    arguments=dict(arguments or {}),
                )
            )
    return "".join(text_parts), [part for part in content_parts if part.type != "text"], tool_calls


def gemini_part(part: ContentPart) -> dict[str, Any]:
    """Convert a content part to Gemini generateContent API format."""
    if part.type == "text":
        return {"text": part.text or ""}
    inline = part_inline_bytes(part)
    if inline is not None:
        mime_type, encoded = inline
        return {"inlineData": {"mimeType": mime_type, "data": encoded}}
    remote = part_remote_uri(part)
    if remote is not None:
        return {"fileData": {"mimeType": part.mime_type or "application/octet-stream", "fileUri": remote}}
    raise ValueError(f"{part.type} part requires uri or inline data")


def gemini_message(message: Message) -> dict[str, Any]:
    """Convert a message to Gemini contents format."""
    if message.role == "tool" and message.tool_result is not None:
        return {
            "role": "user",
            "parts": [
                {
                    "functionResponse": {
                        "name": message.tool_result.call_id,
                        "response": {
                            "output": message.tool_result.error
                            or content_text(
                                str(message.tool_result.content) if message.tool_result.content is not None else "",
                                message.tool_result.content_parts,
                            )
                        },
                    }
                }
            ],
        }
    if message.role == "assistant" and message.tool_calls:
        parts: list[dict[str, Any]] = []
        if message.content:
            parts.append({"text": message.content})
        for call in message.tool_calls:
            parts.append({"functionCall": {"name": call.name, "args": call.arguments}})
        return {"role": "model", "parts": parts}
    parts = normalize_content_parts(message.content_parts)
    if not parts:
        parts = [ContentPart(type="text", text=message.content)]
    elif message.content and not any(part.type == "text" and part.text == message.content for part in parts):
        parts = [ContentPart(type="text", text=message.content)] + parts
    role = "model" if message.role == "assistant" else "user"
    return {"role": role, "parts": [gemini_part(part) for part in parts]}


def parse_gemini_parts(parts: list[Any]) -> tuple[str, list[ContentPart], list[ToolCall]]:
    """Parse Gemini candidate parts into Logos content."""
    text_parts: list[str] = []
    content_parts: list[ContentPart] = []
    tool_calls: list[ToolCall] = []
    for part in parts:
        if not isinstance(part, dict):
            continue
        if "text" in part:
            text = str(part.get("text") or "")
            text_parts.append(text)
            continue
        if "functionCall" in part:
            call = part.get("functionCall") or {}
            tool_calls.append(
                ToolCall(
                    id=str(call.get("id") or call.get("name") or ""),
                    name=str(call.get("name") or ""),
                    arguments=dict(call.get("args") or {}),
                )
            )
            continue
        if "inlineData" in part:
            inline = part.get("inlineData") or {}
            mime_type = inline.get("mimeType")
            kind = "image"
            if isinstance(mime_type, str):
                if mime_type.startswith("video/"):
                    kind = "video"
                elif mime_type.startswith("audio/"):
                    kind = "audio"
                elif not mime_type.startswith("image/"):
                    kind = "file"
            content_parts.append(
                ContentPart(
                    type=kind,
                    data=inline.get("data"),
                    mime_type=mime_type,
                )
            )
            continue
        if "fileData" in part:
            file_data = part.get("fileData") or {}
            mime_type = file_data.get("mimeType")
            kind = "file"
            if isinstance(mime_type, str):
                if mime_type.startswith("image/"):
                    kind = "image"
                elif mime_type.startswith("video/"):
                    kind = "video"
                elif mime_type.startswith("audio/"):
                    kind = "audio"
            content_parts.append(
                ContentPart(
                    type=kind,
                    uri=file_data.get("fileUri"),
                    mime_type=mime_type,
                )
            )
    return "".join(text_parts), content_parts, tool_calls
