import json
import os
from pathlib import Path
from typing import Any

import requests
from dotenv import load_dotenv

load_dotenv(Path(__file__).resolve().parent.parent / ".env")
load_dotenv()

DEFAULT_BASE_URL = "https://ap-northeast-1.recall.ai/api/v1"


def _base_url() -> str:
    return os.getenv("RECALL_BASE_URL", DEFAULT_BASE_URL).rstrip("/")


def _headers(content_type: bool = True) -> dict[str, str]:
    api_key = os.getenv("RECALL_API_KEY")
    if not api_key:
        raise RuntimeError("RECALL_API_KEY is not configured")

    headers = {
        "Authorization": f"Token {api_key}",
        "accept": "application/json",
    }
    if content_type:
        headers["Content-Type"] = "application/json"
    return headers


def create_bot(
    meeting_url: str,
    bot_name: str | None = None,
    metadata: dict[str, Any] | None = None,
    join_at: str | None = None,
) -> dict[str, Any]:
    payload = {
        "meeting_url": meeting_url,
        "bot_name": bot_name or os.getenv("RECALL_BOT_NAME", "MeetNotemaker Bot"),
        "metadata": metadata or {},
        "recording_config": {
            "retention": {
                "type": "timed",
                "hours": 96
            },
            "audio_mixed_mp3": {},
            "transcript": {
                "provider": {
                    "meeting_captions": {}
                }
            }
        }
    }
    if join_at:
        payload["join_at"] = join_at

    response = requests.post(
        f"{_base_url()}/bot",
        headers=_headers(),
        json=payload,
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def get_bot(bot_id: str) -> dict[str, Any]:
    response = requests.get(
        f"{_base_url()}/bot/{bot_id}/",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def leave_call(bot_id: str) -> dict[str, Any]:
    response = requests.post(
        f"{_base_url()}/bot/{bot_id}/leave_call/",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    try:
        return response.json()
    except ValueError:
        return {"success": True}


def delete_bot(bot_id: str) -> dict[str, Any]:
    response = requests.delete(
        f"{_base_url()}/bot/{bot_id}/",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    if not response.content:
        return {"success": True}
    try:
        return response.json()
    except ValueError:
        return {"success": True}


def delete_bot_media(bot_id: str) -> dict[str, Any]:
    response = requests.post(
        f"{_base_url()}/bot/{bot_id}/delete_media/",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    if not response.content:
        return {"success": True}
    try:
        return response.json()
    except ValueError:
        return {"success": True}


def stop_bot(bot_id: str) -> dict[str, Any]:
    try:
        bot_data = get_bot(bot_id)
    except requests.HTTPError as exc:
        if exc.response is not None and exc.response.status_code == 404:
            return {
                "stopped": True,
                "method": "already_gone",
                "bot_status": None,
            }
        raise

    bot_status = _extract_bot_status(bot_data)
    terminal_statuses = {
        "done",
        "bot.done",
        "call_ended",
        "bot.call_ended",
        "fatal",
        "bot.fatal",
        "recording.done",
        "recording.failed",
        "transcript.done",
        "transcript.failed",
    }
    scheduled_statuses = {"scheduled", "pending"}

    if bot_status in terminal_statuses:
        return {
            "stopped": True,
            "method": "already_terminal",
            "bot_status": bot_status,
        }

    if bot_status in scheduled_statuses:
        try:
            delete_bot(bot_id)
            return {
                "stopped": True,
                "method": "delete_bot",
                "bot_status": bot_status,
            }
        except requests.HTTPError as exc:
            if exc.response is not None and exc.response.status_code == 404:
                return {
                    "stopped": True,
                    "method": "already_gone",
                    "bot_status": bot_status,
                }
            raise

    leave_call(bot_id)
    return {
        "stopped": True,
        "method": "leave_call",
        "bot_status": bot_status,
    }


def create_transcript(recording_id: str, language_code: str = "auto") -> dict[str, Any]:
    payload = {
        "provider": {
            "recallai_async": {
                "language_code": language_code
            }
        }
    }

    response = requests.post(
        f"{_base_url()}/recording/{recording_id}/create_transcript/",
        headers=_headers(),
        json=payload,
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def get_recording(recording_id: str) -> dict[str, Any]:
    response = requests.get(
        f"{_base_url()}/recording/{recording_id}",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def get_transcript(transcript_id: str) -> dict[str, Any]:
    response = requests.get(
        f"{_base_url()}/transcript/{transcript_id}",
        headers=_headers(content_type=False),
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def download_transcript_json(download_url: str) -> Any:
    response = requests.get(download_url, timeout=120)
    response.raise_for_status()
    return response.json()


def normalize_transcript_payload(payload: Any) -> dict[str, Any]:
    segments = _extract_segments(payload)
    normalized_segments = []
    participants = []
    speaker_map: dict[str, str] = {}

    for index, segment in enumerate(segments):
        speaker = _extract_speaker(segment) or "Speaker"
        text = _extract_text(segment)
        if not text:
            continue

        normalized_segment = {
            "index": index,
            "speaker": speaker,
            "text": text,
            "start": _extract_time(segment, ["start", "start_time", "start_ts", "start_seconds"]),
            "end": _extract_time(segment, ["end", "end_time", "end_ts", "end_seconds"]),
        }
        normalized_segments.append(normalized_segment)
        if speaker not in participants:
            participants.append(speaker)
        speaker_map.setdefault(speaker, speaker)

    full_text = " ".join(segment["text"] for segment in normalized_segments).strip()

    return {
        "segments": normalized_segments,
        "participants": participants,
        "speaker_map": speaker_map,
        "full_text": full_text,
        "raw_payload": payload,
    }


def _extract_segments(payload: Any) -> list[dict[str, Any]]:
    if isinstance(payload, list):
        return [item for item in payload if isinstance(item, dict)]

    if not isinstance(payload, dict):
        return []

    for key in ["transcript", "segments", "utterances"]:
        value = payload.get(key)
        if isinstance(value, list):
            return [item for item in value if isinstance(item, dict)]

    monologues = payload.get("monologues")
    if isinstance(monologues, list):
        flattened: list[dict[str, Any]] = []
        for item in monologues:
            if not isinstance(item, dict):
                continue
            segments = item.get("segments")
            if isinstance(segments, list):
                for segment in segments:
                    if isinstance(segment, dict):
                        merged = dict(segment)
                        if "speaker" not in merged and item.get("speaker"):
                            merged["speaker"] = item.get("speaker")
                        if "speaker_label" not in merged and item.get("speaker_label"):
                            merged["speaker_label"] = item.get("speaker_label")
                        flattened.append(merged)
            else:
                flattened.append(item)
        return flattened

    return []


def _extract_speaker(segment: dict[str, Any]) -> str | None:
    participant = segment.get("participant")
    if isinstance(participant, dict):
        for key in ["name", "display_name", "full_name", "participant_name"]:
            value = participant.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()

    for key in ["speaker", "speaker_name", "speaker_label", "participant_name", "participant"]:
        value = segment.get(key)
        if isinstance(value, str) and value.strip():
            return value.strip()
    return None


def _extract_text(segment: dict[str, Any]) -> str:
    for key in ["text", "sentence", "utterance"]:
        value = segment.get(key)
        if isinstance(value, str) and value.strip():
            return value.strip()

    words = segment.get("words")
    if isinstance(words, list):
        tokens = []
        for word in words:
            if isinstance(word, dict):
                token = word.get("text") or word.get("word")
                if isinstance(token, str) and token.strip():
                    tokens.append(token.strip())
            elif isinstance(word, str) and word.strip():
                tokens.append(word.strip())
        return " ".join(tokens).strip()

    return ""


def _extract_bot_status(bot_data: dict[str, Any]) -> str:
    status_changes = bot_data.get("status_changes") or []
    if status_changes:
        return status_changes[-1].get("code") or "pending"

    status = bot_data.get("status") or {}
    if isinstance(status, dict):
        return status.get("code") or "pending"

    return "pending"


def _extract_time(segment: dict[str, Any], keys: list[str]) -> float | None:
    for key in keys:
        value = segment.get(key)
        if isinstance(value, (int, float)):
            return float(value)
        if isinstance(value, dict):
            relative = value.get("relative")
            if isinstance(relative, (int, float)):
                return float(relative)
            if isinstance(relative, str):
                try:
                    return float(relative)
                except ValueError:
                    pass
        if isinstance(value, str):
            try:
                return float(value)
            except ValueError:
                continue

    words = segment.get("words")
    if isinstance(words, list) and words:
        first_word = words[0] if isinstance(words[0], dict) else None
        last_word = words[-1] if isinstance(words[-1], dict) else None
        if "start" in keys and first_word:
            start_value = first_word.get("start_timestamp")
            if isinstance(start_value, dict) and isinstance(start_value.get("relative"), (int, float)):
                return float(start_value["relative"])
        if "end" in keys and last_word:
            end_value = last_word.get("end_timestamp")
            if isinstance(end_value, dict) and isinstance(end_value.get("relative"), (int, float)):
                return float(end_value["relative"])
    return None


def transcript_content_needs_refresh(transcript_json: str | None, speaker_map_json: str | None) -> bool:
    if not transcript_json:
        return True

    try:
        segments = json.loads(transcript_json)
    except json.JSONDecodeError:
        return True

    if not isinstance(segments, list) or not segments:
        return True

    speakers = []
    for segment in segments:
        if isinstance(segment, dict):
            speaker = segment.get("speaker")
            if isinstance(speaker, str) and speaker.strip():
                speakers.append(speaker.strip())

    if not speakers:
        return True

    if all(_looks_generic_speaker(speaker) for speaker in speakers):
        return True

    if speaker_map_json:
        try:
            speaker_map = json.loads(speaker_map_json)
            if isinstance(speaker_map, dict) and speaker_map:
                mapped_values = [str(value).strip() for value in speaker_map.values() if isinstance(value, str)]
                if mapped_values and all(_looks_generic_speaker(value) for value in mapped_values):
                    return True
        except json.JSONDecodeError:
            return True

    return False


def _looks_generic_speaker(value: str) -> bool:
    normalized = value.strip().lower()
    return normalized.startswith("speaker") or normalized.startswith("participant")


def latest_recording(bot_data: dict) -> dict[str, Any] | None:
    recordings = bot_data.get("recordings") or []
    if not recordings:
        return None
    return recordings[-1]
