"""OpenAI vector-store helpers (ported from CrystalliseAppsToolbox store.py).

All Streamlit / session_state dependencies have been removed.
Functions accept explicit parameters instead.
"""
from __future__ import annotations

import time
from datetime import datetime
from typing import Any, Callable, Dict, Optional, Tuple

from openai import OpenAI

from crystallise.llm.retry import call_with_retries


# ---------------------------------------------------------------------------
# Chat-completion kwarg normalisation
# ---------------------------------------------------------------------------

def normalize_chat_completion_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]:
    """Normalise chat-completion kwargs for newer OpenAI models (e.g. GPT-5).

    * Converts ``max_tokens`` to ``max_completion_tokens`` when needed.
    * Strips ``temperature`` for GPT-5 family models.
    """
    normalized = dict(kwargs)

    if "max_tokens" in normalized and "max_completion_tokens" not in normalized:
        normalized["max_completion_tokens"] = normalized.pop("max_tokens")
    elif "max_tokens" in normalized and "max_completion_tokens" in normalized:
        normalized.pop("max_tokens", None)

    model_name = str(normalized.get("model", "")).lower()
    if model_name.startswith("gpt-5") and "temperature" in normalized:
        normalized.pop("temperature", None)

    return normalized


# ---------------------------------------------------------------------------
# Vector-store CRUD
# ---------------------------------------------------------------------------

def create_vector_store(
    client: OpenAI,
    name: str = "VectorStore",
    ttl_days: int = 1,
) -> str:
    """Create a new vector store and return its ID."""
    vs = call_with_retries(
        client.vector_stores.create,
        name=name,
        expires_after={"anchor": "last_active_at", "days": ttl_days},
    )
    return vs.id


def add_file_to_store(
    client: OpenAI,
    vector_store_id: str,
    file_bytes: bytes,
    filename: str,
    timeout_seconds: int = 300,
    progress_callback: Optional[Callable[[str], Any]] = None,
) -> Tuple[str, Dict[str, Any]]:
    """Upload *file_bytes* into an existing vector store.

    Returns ``(file_id, metadata_dict)``.
    """
    file_size_mb = len(file_bytes) / (1024 * 1024)

    if progress_callback:
        progress_callback(f"Uploading {filename} ({file_size_mb:.1f} MB)...")

    uploaded = call_with_retries(
        client.files.create,
        file=(filename, file_bytes),
        purpose="assistants",
    )
    fid = uploaded.id

    if progress_callback:
        progress_callback(f"File uploaded (ID: {fid[:12]}...), adding to vector store...")

    call_with_retries(
        client.vector_stores.files.create,
        vector_store_id=vector_store_id,
        file_id=fid,
    )

    if progress_callback:
        progress_callback("Waiting for indexing to complete...")

    start_time = time.time()
    last_status: Optional[str] = None
    poll_count = 0

    while True:
        elapsed = time.time() - start_time
        if elapsed > timeout_seconds:
            raise TimeoutError(
                f"Indexing timed out after {timeout_seconds}s for {filename} "
                f"(last status: {last_status}, size: {file_size_mb:.1f} MB, polls: {poll_count})"
            )
        try:
            vs_file = client.vector_stores.files.retrieve(
                vector_store_id=vector_store_id, file_id=fid,
            )
            poll_count += 1
            status = vs_file.status
            last_status = status

            if progress_callback:
                progress_callback(
                    f"Indexing {filename}: {status} ({int(elapsed)}s, poll #{poll_count})"
                )

            if status == "completed":
                break
            if status == "failed":
                error_msg = getattr(vs_file, "last_error", None)
                raise RuntimeError(
                    f"Indexing failed for {filename}: {error_msg or 'unknown error'}"
                )
            if status == "cancelled":
                raise RuntimeError(f"Indexing was cancelled for {filename}")

            time.sleep(3.0)
        except Exception as exc:
            if "timeout" in str(exc).lower() or "timed out" in str(exc).lower():
                raise
            if progress_callback:
                progress_callback(f"Poll error (retrying): {exc}")
            time.sleep(3.0)

    meta = {
        "Filename": filename,
        "FilesizeBytes": len(file_bytes),
        "Document ID": fid,
        "Vector Store ID": vector_store_id,
        "Created At": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
    }
    return fid, meta


def delete_vector_store(client: OpenAI, vector_store_id: str) -> None:
    """Delete a vector store by ID. Silently ignores errors."""
    try:
        call_with_retries(client.vector_stores.delete, vector_store_id=vector_store_id)
    except Exception:
        pass


def create_scoped_vector_store(
    client: OpenAI,
    file_ids: list[str],
    *,
    ttl_days: int = 1,
    name_prefix: str = "ScopedVS",
    timeout_seconds: int = 60,
) -> str:
    """Create a temporary vector store scoped to *file_ids*.

    Blocks until all files are indexed or *timeout_seconds* is exceeded.
    Returns the new vector-store ID.
    """
    vs = call_with_retries(
        client.vector_stores.create,
        name=name_prefix,
        expires_after={"anchor": "last_active_at", "days": ttl_days},
    )

    for fid in file_ids:
        call_with_retries(
            client.vector_stores.files.create,
            vector_store_id=vs.id,
            file_id=fid,
        )

    start_time = time.time()
    for fid in file_ids:
        while True:
            elapsed = time.time() - start_time
            if elapsed > timeout_seconds:
                raise TimeoutError(
                    f"Scoped vector store indexing timed out after {timeout_seconds}s"
                )
            vs_file = call_with_retries(
                client.vector_stores.files.retrieve,
                vector_store_id=vs.id,
                file_id=fid,
            )
            st = vs_file.status
            if st == "completed":
                break
            if st == "failed":
                error_msg = getattr(vs_file, "last_error", None)
                raise RuntimeError(
                    f"Indexing failed for file {fid} in scoped store {vs.id}: "
                    f"{error_msg or 'unknown'}"
                )
            if st == "cancelled":
                raise RuntimeError(f"Indexing cancelled for file {fid}")
            time.sleep(1.0)

    return vs.id
