import json
import re
from typing import Callable

from analogai.deepthink.infrastructure.gateways.llm.gateway import LlmGateway
from analogai.foundation.settings import config as app_config

from analogai.foundation.common.logging.app_logger import app_logger
from analogai.deepthink.presentation.preprocessing.previous_messages_stack import PreviousMessagesStack
from analogai.deepthink.presentation.parsers.json_sentence_parser import is_json_format
from analogai.deepthink.presentation.preprocessing.preprocessors.llm.transformed_sentence_normalizer import (
    normalize_transformed_sentence,
)
from analogai.deepthink.presentation.preprocessing.preprocessors.llm.transformed_sentence_parser import (
    TransformedSentenceParser,
)
from analogai.deepthink.messaging.text.sentence_chunker import split_into_sentences
from analogai.deepthink.infrastructure.gateways.llm.utils.llm_logging_util import (
    format_json_for_log,
    format_llm_output_value,
    trim_for_log,
)


class SentenceTransformerService:
    def __init__(
        self,
        gateway_factory: Callable[[], LlmGateway],
        prompt_provider: Callable[[], str],
        parser: TransformedSentenceParser | None = None,
    ) -> None:
        self._gateway_factory = gateway_factory
        self._prompt_provider = prompt_provider
        self._parser = parser or TransformedSentenceParser()

    def transform(self, text: str, previous_messages_stack: PreviousMessagesStack) -> str:
        if not text or not text.strip():
            return text

        if is_json_format(text):
            app_logger.info("[SentenceTransformer] Skipping LLM - already JSON format")
            try:
                data = self._parser.parse(text.strip())
                normalized = normalize_transformed_sentence(data)
                return json.dumps(normalized)
            except json.JSONDecodeError as e:
                app_logger.error(
                    "[SentenceTransformer] Invalid JSON input provided. "
                    f"reason={e} input_text={text}"
                )
                return text

        try:
            gateway = self._gateway_factory()

            sanitized_text = _sanitize_sentence(text)
            sanitized_text = _normalize_text(sanitized_text)

            history_pairs = list(previous_messages_stack.get_history_pairs())
            if history_pairs:
                last_role, last_content = history_pairs[-1]
                if last_role == "user" and last_content and last_content.strip() == sanitized_text:
                    history_pairs = history_pairs[:-1]
            sanitized_text = _resolve_pronouns_with_history(sanitized_text, history_pairs)

            history = _format_history(history_pairs)
            instruction = self._prompt_provider()
            try:
                gateway.set_system_prompt(instruction)
            except AttributeError:
                pass

            sentences = split_into_sentences(sanitized_text)
            if not sentences:
                return text
            sentences_per_call = _resolve_sentences_per_llm_call()
            chunks = _chunk_sentences(sentences, sentences_per_call)

            combined_sentences: list[dict] = []
            for chunk in chunks:
                chunk_text = " ".join(part.strip() for part in chunk if part and part.strip()).strip()
                if not chunk_text:
                    continue
                user_message = _build_user_message(history, chunk_text)
                normalized = self._transform_chunk(
                    gateway=gateway,
                    user_message=user_message,
                    original_text=text,
                    history=history,
                )
                if not normalized:
                    continue
                payload_sentences = normalized.get("sentences")
                if isinstance(payload_sentences, list):
                    combined_sentences.extend(payload_sentences)

            if not combined_sentences:
                return text
            return json.dumps({"sentences": combined_sentences})
        except Exception as e:
            app_logger.error(
                "[SentenceTransformer] Error transforming sentence with LLM. "
                f"reason={e} input_text={text}"
            )
            return text

    def _transform_chunk(
        self,
        gateway: LlmGateway,
        user_message: str,
        original_text: str,
        history: list[str],
    ) -> dict | None:
        app_logger.info(
            "[SentenceTransformer] LLM call\n%s",
            format_json_for_log({
                "input": trim_for_log(user_message),
            }),
        )
        transformed = gateway.generate_completion(user_message)
        output_for_log = format_llm_output_value(
            self._parser.format_for_log(transformed),
        )
        app_logger.info(
            "[SentenceTransformer] LLM output\n%s",
            format_json_for_log({
                "output": output_for_log,
            }),
        )
        try:
            data = self._parser.parse(transformed)
        except json.JSONDecodeError as e:
            app_logger.warning(
                "[SentenceTransformer] Invalid JSON from LLM, retrying once. "
                f"reason={e} input_text={original_text}"
            )
            retry_transformed = gateway.generate_completion(user_message)
            retry_output_for_log = format_llm_output_value(
                self._parser.format_for_log(retry_transformed),
            )
            app_logger.info(
                "[SentenceTransformer] LLM retry output\n%s",
                format_json_for_log({
                    "output": retry_output_for_log,
                }),
            )
            try:
                data = self._parser.parse(retry_transformed)
                transformed = retry_transformed
            except json.JSONDecodeError as retry_error:
                app_logger.error(
                    "[SentenceTransformer] LLM returned invalid JSON. "
                    f"reason={retry_error} input_text={original_text} history={history} "
                    f"user_message={user_message} llm_output={retry_transformed}"
                )
                return None
        return normalize_transformed_sentence(data)


def _sanitize_sentence(text: str) -> str:
    sanitized_text = text.replace('"', "").strip()
    if sanitized_text.startswith(("- ", "* ", "• ")):
        sanitized_text = sanitized_text[2:].strip()
    sanitized_text = re.sub(r"^\d+[\.)]\s+", "", sanitized_text)
    return sanitized_text


def _normalize_text(text: str) -> str:
    if text is None:
        return ""
    return " ".join(text.strip().split())


def _format_history(history_pairs: list[tuple[str, str]]) -> list[str]:
    history = []
    for role, content in history_pairs:
        if not content or not content.strip():
            continue
        label = "Q" if role == "user" else "A"
        history.append(f"{label}: {content.strip()}")
    return history


def _resolve_pronouns_with_history(sentence: str, history_pairs: list[tuple[str, str]]) -> str:
    if not sentence or not history_pairs:
        return sentence
    last_subject = _extract_last_subject(history_pairs)
    if not last_subject:
        return sentence
    replacements = {
        " he ": f" {last_subject} ",
        " she ": f" {last_subject} ",
        " they ": f" {last_subject} ",
        " him ": f" {last_subject} ",
        " her ": f" {last_subject} ",
    }
    padded = f" {sentence.strip()} "
    lowered = padded.lower()
    for token, replacement in replacements.items():
        if token in lowered:
            lowered = lowered.replace(token, replacement)
    return lowered.strip()


def _extract_last_subject(history_pairs: list[tuple[str, str]]) -> str | None:
    for role, content in reversed(history_pairs):
        if role != "user" or not content:
            continue
        text = content.strip()
        for sep in (" is ", " are "):
            if sep in text:
                return text.split(sep)[0].strip()
        return text.split()[0] if text else None
    return None




def _resolve_sentences_per_llm_call(default_value: int = 3) -> int:
    configured = app_config.get("limits.sentences_per_llm_call", default_value)
    try:
        configured_int = int(configured)
    except (TypeError, ValueError):
        configured_int = default_value
    return max(1, configured_int)


def _chunk_sentences(sentences: list[str], max_per_chunk: int) -> list[list[str]]:
    if not sentences:
        return []
    max_per_chunk = max(1, int(max_per_chunk))
    return [sentences[i:i + max_per_chunk] for i in range(0, len(sentences), max_per_chunk)]


def _build_user_message(history: list[str], sentence_text: str) -> str:
    if history:
        history_text = "\n".join(history)
        return (
            f"Conversation history (for context only): {history_text}\n\n"
            f"Sentence: {sentence_text}"
        )
    return f"Sentence: {sentence_text}"
