from datetime import timedelta
from urllib.parse import urlparse

import httpx

from reflector.db.rooms import rooms_controller
from reflector.db.transcripts import Transcript, transcripts_controller
from reflector.settings import settings


class InvalidMessageError(Exception):
    pass


async def get_zulip_topics(stream_id: int) -> list[dict]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"https://{settings.ZULIP_REALM}/api/v1/users/me/{stream_id}/topics",
                auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
            )

            response.raise_for_status()

            return response.json().get("topics", [])
    except httpx.RequestError as error:
        raise Exception(f"Failed to get topics: {error}")


async def get_zulip_streams() -> list[dict]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"https://{settings.ZULIP_REALM}/api/v1/streams",
                auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
            )

            response.raise_for_status()

            return response.json().get("streams", [])
    except httpx.RequestError as error:
        raise Exception(f"Failed to get streams: {error}")


async def send_message_to_zulip(stream: str, topic: str, content: str):
    try:
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"https://{settings.ZULIP_REALM}/api/v1/messages",
                data={
                    "type": "stream",
                    "to": stream,
                    "topic": topic,
                    "content": content,
                },
                auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )

            response.raise_for_status()

            return response.json()
    except httpx.RequestError as error:
        raise Exception(f"Failed to send message to Zulip: {error}")


async def update_zulip_message(message_id: int, stream: str, topic: str, content: str):
    try:
        async with httpx.AsyncClient() as client:
            response = await client.patch(
                f"https://{settings.ZULIP_REALM}/api/v1/messages/{message_id}",
                data={
                    "topic": topic,
                    "content": content,
                },
                auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )

            if (
                response.status_code == 400
                and response.json()["msg"] == "Invalid message(s)"
            ):
                raise InvalidMessageError(f"There is no message with id: {message_id}")

            response.raise_for_status()

            return response.json()
    except httpx.RequestError as error:
        raise Exception(f"Failed to update Zulip message: {error}")


def get_zulip_message(transcript: Transcript, include_topics: bool):
    transcript_url = f"{settings.UI_BASE_URL}/transcripts/{transcript.id}"

    header_text = f"# Reflector – {transcript.title or 'Unnamed recording'}\n\n"
    header_text += f"**Date**: <time:{transcript.created_at.isoformat()}>\n"
    header_text += f"**Link**: [{extract_domain(transcript_url)}]({transcript_url})\n"
    header_text += f"**Duration**: {format_time_ms(transcript.duration)}\n\n"

    topic_text = ""

    if include_topics and transcript.topics:
        topic_text = "```spoiler Topics\n"
        for topic in transcript.topics:
            topic_text += f"1. [{format_time(topic.timestamp)}] {topic.title}\n"
        topic_text += "```\n\n"

    summary = "```spoiler Summary\n"
    summary += transcript.long_summary or "No summary available"
    summary += "\n```\n\n"

    message = header_text + summary + topic_text + "-----\n"
    return message


async def post_transcript_notification(transcript: Transcript) -> int | None:
    """Post or update transcript notification in Zulip.

    Uses transcript.room_id directly (Hatchet flow).
    Celery's pipeline_post_to_zulip uses recording→meeting→room path instead.
    DUPLICATION NOTE: This function will stay when we use Celery no more, and Celery one will be removed.
    """
    if not transcript.room_id:
        return None

    room = await rooms_controller.get_by_id(transcript.room_id)
    if not room or not room.zulip_stream or not room.zulip_auto_post:
        return None

    message = get_zulip_message(transcript=transcript, include_topics=True)
    message_updated = False

    if transcript.zulip_message_id:
        try:
            await update_zulip_message(
                transcript.zulip_message_id,
                room.zulip_stream,
                room.zulip_topic,
                message,
            )
            message_updated = True
        except Exception:
            pass

    if not message_updated:
        response = await send_message_to_zulip(
            room.zulip_stream, room.zulip_topic, message
        )
        message_id = response.get("id")
        if message_id:
            await transcripts_controller.update(
                transcript, {"zulip_message_id": message_id}
            )
        return message_id

    return transcript.zulip_message_id


def extract_domain(url: str) -> str:
    return urlparse(url).netloc


def format_time_ms(milliseconds: float) -> str:
    return format_time(milliseconds // 1000)


def format_time(seconds: float) -> str:
    td = timedelta(seconds=seconds)
    time = str(td - timedelta(microseconds=td.microseconds))

    return time[2:] if time.startswith("0:") else time
