import json
import pickle
import re
from pathlib import Path
from typing import List

import faiss
import numpy as np
from langchain_core.embeddings import Embeddings
from sentence_transformers import SentenceTransformer

EMBED_MODEL = "intfloat/multilingual-e5-large"
DATA_DIR = Path(__file__).parent
TICKETS_PATTERN = "tickets*.json"
VECTOR_DIR = DATA_DIR / "vector_store"
VECTOR_INDEX = VECTOR_DIR / "index.faiss.bin"
VECTOR_META = VECTOR_DIR / "metadata.pkl"

NON_SOLUTION = re.compile(
    r"(уточнит|приложите скрин|какая ошибка|с какой проблемой"
    r"|нет обратной связи|запрос завершу|открыть его снова"
    r"|откройте новый|обратиться в службу поддержки"
    r"|свяжитесь с технической"
    r"|напишите нам|позвоните нам)", re.I
)


class LocalEmbeddings(Embeddings):
    def __init__(self, model_name: str):
        self.model = SentenceTransformer(model_name, device="cuda")

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        emb = self.model.encode(texts, normalize_embeddings=True, show_progress_bar=True)
        return emb.tolist()

    def embed_query(self, text: str) -> List[float]:
        emb = self.model.encode([text], normalize_embeddings=True)
        return emb[0].tolist()


def clean_text(text: str) -> str:
    text = re.sub(r'\[/?q\].*?\[/q\]', '', text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r'https?://\S+', '', text)
    text = re.sub(r'\s+', ' ', text).strip()
    return text


def has_real_solution(messages: list[dict]) -> str | None:
    support_texts = []
    for m in messages:
        if m["role"] == "support":
            cleaned = clean_text(m["text"])
            if cleaned:
                support_texts.append(cleaned)
    if not support_texts:
        return None
    combined = "\n".join(support_texts)
    if len(combined) < 100:
        return None
    last = support_texts[-1]
    if NON_SOLUTION.search(last):
        return None
    if last.strip().endswith("?") and len(last) < 150:
        return None
    return combined


def get_client_dialogue(messages: list[dict]) -> str:
    texts = []
    for m in messages:
        if m["role"] == "client":
            cleaned = clean_text(m["text"])
            if cleaned:
                texts.append(cleaned)
    return "\n".join(texts)


def build_chunk(ticket: dict) -> dict | None:
    msgs = ticket.get("messages") or []
    if not msgs:
        return None
    description = clean_text(ticket.get("description", ""))
    solution = has_real_solution(msgs)
    if not solution:
        return None

    client_msgs = get_client_dialogue(msgs)

    # Текст для поиска — только описание + переписка клиента
    search_parts = []
    if description:
        search_parts.append(description)
    if client_msgs:
        search_parts.append(client_msgs)
    search_text = "\n".join(search_parts).strip()
    if not search_text:
        return None

    # Полный текст для показа и LLM
    full_text = (
        f"Категория: {ticket['category']}\n"
        f"Проблема: {description}\n"
        f"Решение: {solution}"
    )

    metadata = {
        "ticket_id": ticket["ticket_id"],
        "client": ticket["client"],
        "category": ticket["category"],
        "full_text": full_text,
    }
    return {"search_text": search_text, "metadata": metadata}


def load_all_tickets() -> list:
    all_tickets = []
    for f in sorted(DATA_DIR.glob(TICKETS_PATTERN)):
        tickets = json.loads(f.read_text(encoding="utf-8"))
        print(f"  {f.name}: {len(tickets)} тикетов")
        all_tickets.extend(tickets)
    return all_tickets


def save_faiss_index(index, path: Path):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(faiss.serialize_index(index))


def load_faiss_index(path: Path):
    return faiss.deserialize_index(np.frombuffer(path.read_bytes(), dtype=np.uint8))


def main():
    print("Загрузка файлов tickets*.json ...")
    tickets = load_all_tickets()
    print(f"Всего тикетов: {len(tickets)}")

    chunks = []
    skipped = 0
    for t in tickets:
        chunk = build_chunk(t)
        if chunk is not None:
            chunks.append(chunk)
        else:
            skipped += 1
    print(f"Сформировано чанков: {len(chunks)}")
    print(f"Пропущено (нет решения): {skipped}")

    print(f"Загрузка модели эмбеддингов: {EMBED_MODEL} ...")
    embeddings = LocalEmbeddings(EMBED_MODEL)

    search_texts = [c["search_text"] for c in chunks]
    metadatas = [c["metadata"] for c in chunks]

    print("Создание эмбеддингов и FAISS индекса...")
    vectors = embeddings.embed_documents(search_texts)
    dim = len(vectors[0])
    index = faiss.IndexFlatIP(dim)
    index.add(np.array(vectors, dtype=np.float32))

    save_faiss_index(index, VECTOR_INDEX)
    with open(VECTOR_META, "wb") as f:
        pickle.dump({"metadatas": metadatas}, f)

    print(f"FAISS индекс сохранён: {VECTOR_DIR}")
    print(f"Размерность: {dim}")
    print(f"Векторов в индексе: {index.ntotal}")


if __name__ == "__main__":
    main()
