"""Optional OpenRouter-backed business-aware reply generator."""
from __future__ import annotations

import json
from typing import Any, Dict, Optional

import httpx

from app.action_llm_client import extract_first_json_object
from app.business_knowledge_service import KnowledgeMatch
from app.config import settings
from app.message_cleaner import extract_customer_reply_text
from app.reply_recipient_utils import apply_preferred_greeting, resolve_reply_recipient


class LLMReplyError(Exception):
    pass


def _trim(text: str, max_chars: int) -> str:
    text = str(text or "").strip()
    if len(text) <= max_chars:
        return text
    return text[: max_chars - 1] + "…"


_COMPANY_MARKERS = {"lda", "l.da", "sa", "s.a", "unipessoal", "limitada", "empresa", "condomínio", "condominio"}
_FEMALE_FIRST_NAMES = {
    "ana", "barbara", "bárbara", "beatriz", "catarina", "claudia", "cláudia", "cristina",
    "daniela", "filipa", "ines", "inês", "joana", "maria", "mariana", "marta",
    "patricia", "patrícia", "rita", "sara", "sofia", "susana", "teresa", "vera",
}
_MALE_FIRST_NAMES = {
    "antonio", "antónio", "bruno", "carlos", "diogo", "duarte", "fernando", "francisco",
    "joao", "joão", "jose", "josé", "luis", "luís", "manuel", "miguel", "nuno",
    "paulo", "pedro", "ricardo", "rui", "sergio", "sérgio", "tiago", "vasco",
}


def _looks_like_person_name(name: str) -> bool:
    name = str(name or "").strip()
    if not name or "@" in name:
        return False
    lowered = name.lower().replace(",", " ")
    if any(marker in lowered.split() for marker in _COMPANY_MARKERS):
        return False
    return bool(name.split())


def _preferred_greeting(customer_name: str, customer_message: str) -> str:
    """Return a readable greeting to guide/normalize LLM replies.

    Use a title only when the first name is strongly recognized. Otherwise prefer
    the full name without title rather than inventing gender.
    """
    raw_name = " ".join(str(customer_name or "").split()).strip()
    message = str(customer_message or "").lower()
    if "boa tarde" in message:
        base = "Boa tarde"
    elif "boa noite" in message:
        base = "Boa noite"
    else:
        base = "Bom dia"

    if not _looks_like_person_name(raw_name):
        return f"{base},"

    first = raw_name.split()[0].strip().lower()
    if first in _FEMALE_FIRST_NAMES:
        return f"{base} Sra. {raw_name},"
    if first in _MALE_FIRST_NAMES:
        return f"{base} Sr. {raw_name},"
    return f"{base} {raw_name},"


def _apply_preferred_greeting(message_body: str, preferred_greeting: str) -> str:
    return apply_preferred_greeting(message_body, preferred_greeting)


def build_reply_system_prompt() -> str:
    return """És o assistente de respostas da BLIF dentro do Clientflow.

Objetivo:
- Interpretar a intenção real da mensagem limpa do cliente.
- Usar contexto da tarefa/oportunidade e conhecimento BLIF aprovado.
- Gerar um rascunho curto, profissional e editável para o operador.

Regras obrigatórias:
- Usa apenas factos fornecidos no contexto, conhecimento BLIF e documentos/oportunidade.
- Não inventes preços, stock, IVA, descontos, prazos, anexos ou condições comerciais.
- Não transformes todo pedido em lista de equipamentos. Primeiro responde ao que o cliente pediu.
- A BLIF fabrica/fornece carregadores e acessórios; não presta serviço direto de instalação.
- Se o cliente perguntar sobre instalação, esclarece que o valor do equipamento não inclui instalação salvo indicação explícita e que qualquer eletricista qualificado pode instalar; a BLIF pode prestar suporte remoto.
- Se o pedido for apenas esclarecimento, não peças anexos nem digas "segue em anexo".
- Se for pedido de documento/anexo e não houver documento selecionado, marca requires_attachment=true e explica em warnings.
- Se não houver informação suficiente para responder com segurança, usa reply_type=manual_review e confidence baixo.
- Devolve APENAS JSON válido com as chaves: intent, reply_type, requires_attachment, confidence, customer_need, recommended_next_action, message_body, knowledge_used, warnings.

Tipos de reply_type permitidos:
- answer_without_attachment
- answer_with_links_or_attachment
- send_document
- operational_ack
- support_ack
- no_customer_reply
- manual_review

Formato visual obrigatório para message_body:
- Começar exatamente com context.preferred_greeting quando esse campo existir.
- Usar recipient.person_name como pessoa destinatária quando existir; não cumprimentar com nome de empresa, cliente fiscal, LDA, SA ou UNIPESSOAL.
- Usar cumprimento formal quando context.preferred_greeting incluir “Sr.” ou “Sra.”; não trocar por “Olá”.
- Usar parágrafos curtos e fáceis de ler.
- Em pedidos de preço/proposta, usar estrutura como proposta comercial:
  cumprimento, agradecimento, equipamento/valor s/IVA, funcionalidades incluídas, prazo de entrega quando conhecido e próximo passo.
- Usar travessão “–” para linhas de preço, funcionalidades e prazos.
- Em respostas comerciais, terminar com:
  Com os melhores cumprimentos,
  Sérgio Araújo
  Blif
- Se o cliente pedir carregador + instalação, apresentar o equipamento indicado e esclarecer que instalação/cablagem não está incluída e deve ser validada por eletricista qualificado.
- Se houver dados suficientes no catálogo para identificar o modelo provável, usar o preço de catálogo s/IVA; não responder apenas “vamos preparar orçamento”.
- Não inventar cabo extra, metros de cabo fornecidos, stock ou condições de instalação. Se a cablagem for mencionada pelo cliente, dizer que deve ser avaliada pelo eletricista.
- Se o cliente fizer follow-up sobre morada/endereço, não responder genericamente “estamos a analisar”. Confirmar a receção da morada, resumir a morada se estiver na mensagem e indicar o próximo passo concreto.

message_body deve estar em português de Portugal, sem informação repetida e pronto para edição."""


def build_reply_user_prompt(context: Dict[str, Any]) -> str:
    return json.dumps(context, ensure_ascii=False, indent=2, default=str)


def generate_reply_with_openrouter(context: Dict[str, Any]) -> Dict[str, Any]:
    api_key = str(settings.openrouter_api_key or "").strip()
    if not api_key:
        raise LLMReplyError("OPENROUTER_API_KEY não configurada.")
    timeout = float(getattr(settings, "clientflow_reply_llm_timeout_seconds", 20) or 20)
    payload = {
        "model": getattr(settings, "clientflow_reply_llm_model", "") or settings.openrouter_model,
        "messages": [
            {"role": "system", "content": build_reply_system_prompt()},
            {"role": "user", "content": build_reply_user_prompt(context)},
        ],
        "temperature": float(getattr(settings, "clientflow_reply_llm_temperature", 0.2) or 0.2),
        "max_tokens": int(getattr(settings, "clientflow_reply_llm_max_tokens", 700) or 700),
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "HTTP-Referer": "https://clientflow.blif.pt",
        "X-Title": "ClientFlow BLIF Reply Assistant",
    }
    try:
        with httpx.Client(timeout=timeout) as client:
            response = client.post(settings.openrouter_url, headers=headers, json=payload)
        if response.status_code >= 400:
            raise LLMReplyError(f"OpenRouter HTTP {response.status_code}: {response.text[:300]}")
        data = response.json()
        content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
        if not content:
            raise LLMReplyError("OpenRouter não devolveu conteúdo.")
        parsed = json.loads(extract_first_json_object(content))
        if not isinstance(parsed, dict):
            raise ValueError("JSON gerado não é objeto")
        return parsed
    except LLMReplyError:
        raise
    except Exception as exc:
        raise LLMReplyError(str(exc)) from exc


def build_llm_reply_context(
    *,
    task: Dict[str, Any],
    template: Dict[str, Any],
    baseline_message: str,
    knowledge: KnowledgeMatch,
    selected_documents: list[Dict[str, Any]],
) -> Dict[str, Any]:
    customer_message = extract_customer_reply_text(task)
    recipient = resolve_reply_recipient(task, cleaned_customer_message=customer_message)
    preferred_greeting = recipient.get("preferred_greeting") or ""
    max_chars = int(getattr(settings, "clientflow_reply_llm_max_context_chars", 5000) or 5000)
    return {
        "recipient": {
            "person_name": recipient.get("person_name") or "",
            "company_name": recipient.get("company_name") or "",
            "greeting_source": recipient.get("greeting_source") or "",
            "rule": "Usar person_name na saudação quando existir. Não usar company_name/fiscal_name como destinatário da saudação.",
        },
        "customer": {
            "name": recipient.get("person_name") or recipient.get("contact_name") or "",
            "contact_name": recipient.get("contact_name") or "",
            "fiscal_name": task.get("linked_customer_name") or "",
            "tax_id": task.get("linked_customer_tax_id") or "",
            "email": task.get("customer_email") or task.get("linked_customer_email") or "",
        },
        "opportunity": {
            "id": task.get("opportunity_id") or "",
            "action_code": task.get("action_code") or "",
            "status": task.get("opportunity_stage") or task.get("status") or "",
            "documents": [
                {
                    "id": doc.get("id"),
                    "kind": doc.get("document_kind"),
                    "number": doc.get("document_number") or doc.get("external_id"),
                    "amount": doc.get("total_amount") or doc.get("amount"),
                    "currency": doc.get("currency") or "EUR",
                }
                for doc in selected_documents
            ],
        },
        "customer_message": _trim(customer_message, max_chars),
        "historico_recente_conversa": task.get("recent_conversation_context") or task.get("conversation_history") or task.get("previous_context") or [],
        "preferred_greeting": preferred_greeting,
        "selected_template": template,
        "baseline_message": baseline_message,
        "relevant_knowledge": knowledge.to_prompt_context(),
        "catalog_reference": {
            "products": list(knowledge.products),
            "accessories": list(knowledge.accessories),
            "note": "Preços de catálogo são sem IVA e podem mudar; usar apenas se relevante para a pergunta.",
        },
        "available_reply_types": [
            "answer_without_attachment",
            "answer_with_links_or_attachment",
            "send_document",
            "operational_ack",
            "support_ack",
            "no_customer_reply",
            "manual_review",
        ],
        "operator_instructions": [
            "Responder em português de Portugal.",
            "Começar a mensagem exatamente com preferred_greeting quando disponível.",
            "Ser curto, claro e profissional.",
            "Formatar a resposta com parágrafos curtos e listas com travessão quando houver preço, funcionalidades ou prazos.",
            "Para pedidos de orçamento, dar uma proposta preliminar quando o catálogo tiver preço relevante.",
            "Não repetir informação desnecessária.",
            "Não inventar informação que não esteja no contexto.",
            "Não dizer que segue anexo quando requires_attachment=false ou não há documento selecionado.",
        ],
    }


def _to_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "sim"}
    return bool(value)


def normalize_llm_reply(raw: Dict[str, Any], *, fallback_message: str, knowledge: KnowledgeMatch, preferred_greeting: str = "") -> Dict[str, Any]:
    message = str(raw.get("message_body") or "").strip() or fallback_message
    message = _apply_preferred_greeting(message, preferred_greeting)
    try:
        confidence = float(raw.get("confidence") or 0.75)
    except Exception:
        confidence = 0.75
    confidence = max(0.0, min(1.0, confidence))
    return {
        "intent": str(raw.get("intent") or (knowledge.primary_topic.id if knowledge.primary_topic else "business_reply")),
        "reply_type": str(raw.get("reply_type") or knowledge.reply_type or "answer_without_attachment"),
        "requires_attachment": _to_bool(raw.get("requires_attachment")),
        "confidence": confidence,
        "customer_need": str(raw.get("customer_need") or ""),
        "recommended_next_action": str(raw.get("recommended_next_action") or ""),
        "message_body": message,
        "knowledge_used": list(raw.get("knowledge_used") or ([knowledge.primary_topic.id] if knowledge.primary_topic else [])),
        "warnings": [str(item) for item in (raw.get("warnings") or []) if str(item).strip()],
    }
