from __future__ import annotations

import asyncio
import json
import logging
import re
import time
from typing import Any, Dict, List

import tiktoken

from .env import load_navexa_env
from ..llm.client import get_llm_client, has_llm_credentials

load_navexa_env()


def _is_fatal_llm_config_error(exc: Exception) -> bool:
    msg = str(exc).lower()
    fatal_signatures = (
        "api_key client option must be set",
        "unknown model",
        "incorrect api key",
        "invalid api key",
        "authentication",
    )
    return any(sig in msg for sig in fatal_signatures)


def _has_llm_credentials() -> bool:
    return has_llm_credentials()


def count_tokens(text: str, model: str | None = None) -> int:
    if not text:
        return 0
    try:
        enc = tiktoken.encoding_for_model(model or "gpt-4o")
    except Exception:
        enc = tiktoken.get_encoding("cl100k_base")
    return len(enc.encode(text))


def ChatGPT_API_with_finish_reason(model: str, prompt: str, chat_history: List[Dict[str, str]] | None = None):
    if not _has_llm_credentials():
        logging.error("LLM disabled: missing credentials for selected provider.")
        return "Error", "finished"
    max_retries = 10
    client = get_llm_client()
    for i in range(max_retries):
        try:
            response = client.chat(model=model, prompt=prompt, chat_history=chat_history)
            finish = response.choices[0].finish_reason
            state = "max_output_reached" if finish == "length" else "finished"
            return response.choices[0].message.content, state
        except Exception as e:
            logging.error("Error: %s", e)
            if _is_fatal_llm_config_error(e):
                return "Error", "finished"
            if i < max_retries - 1:
                time.sleep(1)
            else:
                return "Error", "finished"


def ChatGPT_API(model: str, prompt: str, chat_history: List[Dict[str, str]] | None = None):
    if not _has_llm_credentials():
        logging.error("LLM disabled: missing credentials for selected provider.")
        return "Error"
    max_retries = 10
    client = get_llm_client()
    for i in range(max_retries):
        try:
            response = client.chat(model=model, prompt=prompt, chat_history=chat_history)
            return response.choices[0].message.content
        except Exception as e:
            logging.error("Error: %s", e)
            if _is_fatal_llm_config_error(e):
                return "Error"
            if i < max_retries - 1:
                time.sleep(1)
            else:
                return "Error"


async def ChatGPT_API_async(model: str, prompt: str):
    if not _has_llm_credentials():
        logging.error("LLM disabled: missing credentials for selected provider.")
        return "Error"
    max_retries = 10
    client = get_llm_client()
    for i in range(max_retries):
        try:
            response = await client.chat_async(model=model, prompt=prompt)
            return response.choices[0].message.content
        except Exception as e:
            logging.error("Error: %s", e)
            if _is_fatal_llm_config_error(e):
                return "Error"
            if i < max_retries - 1:
                await asyncio.sleep(1)
            else:
                return "Error"


def get_json_content(response: str) -> str:
    start_idx = response.find("```json")
    if start_idx != -1:
        response = response[start_idx + 7 :]
    end_idx = response.rfind("```")
    if end_idx != -1:
        response = response[:end_idx]
    return response.strip()


def extract_json(content: str) -> Dict[str, Any]:
    try:
        txt = content.strip()
        if "```json" in txt:
            txt = get_json_content(txt)
        txt = txt.replace("None", "null")
        txt = re.sub(r"\s+", " ", txt)
        return json.loads(txt)
    except Exception:
        try:
            txt = txt.replace(",]", "]").replace(",}", "}")
            return json.loads(txt)
        except Exception:
            return {}


def structure_to_list(structure: Any) -> List[Dict[str, Any]]:
    if isinstance(structure, dict):
        nodes = [structure]
        children = structure.get("children") or structure.get("nodes") or []
        nodes.extend(structure_to_list(children))
        return nodes
    if isinstance(structure, list):
        out: List[Dict[str, Any]] = []
        for item in structure:
            out.extend(structure_to_list(item))
        return out
    return []


async def generate_node_summary(node: Dict[str, Any], model: str | None = None) -> str:
    prompt = (
        "You are given a part of a document. Generate a concise summary of its main points.\n\n"
        f"Partial Document Text:\n{node.get('full_text') or node.get('text') or ''}\n\n"
        "Return only the summary text."
    )
    return await ChatGPT_API_async(model or "gpt-4o", prompt)


def get_usage_summary() -> Dict[str, Any]:
    return get_llm_client().tracker.summary()
