"""
A2RAG Client
============
Main entry point for the A2RAG decision layer.

Usage:
    from a2rag import A2RAGClient

    client = A2RAGClient(api_key="a2rag_production_xxx")

    decision = client.decide(
        query="Can I return this item?",
        contexts=["Returns accepted within 14 days for unused items."],
        draft_answer="Yes, you can return it within 14 days.",
    )

    if decision.action == "answer":
        show_to_user(decision.draft_answer)
    elif decision.action == "clarify":
        ask_user(decision.clarification)
    elif decision.action == "abstain":
        escalate_to_human()
"""
from __future__ import annotations

import time
import uuid
from typing import Any, Dict, List, Optional

from .models import Decision, Metrics, CalibrationResult
from .storage import LocalStorage
from .telemetry import Telemetry
from .http import APIClient, _normalize_contexts
from .analytics import Analytics


class A2RAGClient:
    """
    A2RAG decision layer client.

    Parameters
    ----------
    api_key      : Your A2RAG API key
    base_url     : API base URL (default: https://api.a2rag.com)
    storage_path : Path for local decision storage (default: ~/.a2rag/decisions.db)
    telemetry    : Send anonymous usage metrics (default: True for free tier)
    timeout      : Request timeout in seconds (default: 10)
    """

    DEFAULT_URL = "https://api.a2rag.com"

    def __init__(
        self,
        api_key:      str,
        base_url:     str = DEFAULT_URL,
        storage_path: Optional[str] = None,
        telemetry:    bool = True,
        timeout:      int = 10,
    ):
        if not api_key:
            raise ValueError("api_key is required. Get one at https://a2rag.com")

        self._api     = APIClient(api_key=api_key, base_url=base_url, timeout=timeout)
        self._storage = LocalStorage(storage_path)
        self._telemetry = Telemetry(api_key=api_key, enabled=telemetry, base_url=base_url)
        self._analytics = Analytics(self._storage)
        self.api_key  = api_key

    # ── Core decision ─────────────────────────────────────────────

    def decide(
        self,
        query:            str,
        contexts:         List[Any],
        draft_answer:     str,
        domain:           Optional[str]  = None,
        metadata:         Optional[Dict] = None,
        response_fields:  Optional[List[str]] = None,
        field_mapping:    Optional[Dict[str, str]] = None,
        conversation_id:  Optional[str]  = None,
        is_first_message: bool = False,
        is_last_message:  bool = False,
        turn_number:      int  = 0,
    ) -> Decision:
        """
        Make a decision: should the system answer, clarify, or abstain?

        Parameters
        ----------
        query        : The user's question
        contexts     : Retrieved contexts (any format: str, dict, LangChain doc, etc.)
        draft_answer : Your LLM's proposed answer
        domain       : Optional domain hint (insurance/legal/hr/support)
        metadata     : Optional dict stored locally (not sent to API)

        Returns
        -------
        Decision with .action, .clarification, .confidence, .signals, etc.

        Examples
        --------
        # Basic usage
        d = client.decide("Can I return this?", contexts, draft)
        if d.action == "answer": show(draft)

        # With domain
        d = client.decide(query, contexts, draft, domain="insurance")

        # Check specific properties
        if d.should_answer:   show(draft)
        if d.should_clarify:  ask(d.clarification)
        if d.should_abstain:  escalate()
        """
        start = time.time()

        payload = {
            "query":        query,
            "contexts":     _normalize_contexts(contexts),
            "draft_answer": draft_answer,
        }
        if domain:
            payload["domain"] = domain
        if response_fields:
            payload["response_fields"] = response_fields
        if field_mapping:
            payload["field_mapping"] = field_mapping
        if conversation_id:
            payload["conversation_id"] = conversation_id
        if is_first_message:
            payload["is_first_message"] = True
        if is_last_message:
            payload["is_last_message"] = True
        if turn_number:
            payload["turn_number"] = turn_number

        raw = self._api.post("/decide", payload)
        latency_ms = round((time.time() - start) * 1000, 1)

        decision = Decision._from_api(raw, latency_ms=latency_ms, metadata=metadata)

        # Store locally (private — never sent to API)
        self._storage.save_decision(decision)

        # Anonymous telemetry (free tier)
        self._telemetry.record(decision)

        return decision

    # ── Feedback ─────────────────────────────────────────────────

    def feedback(
        self,
        decision_id: str,
        was_correct: bool,
        comment:     Optional[str] = None,
    ) -> bool:
        """
        Send feedback on a decision.

        Only decision_id + correct/incorrect is sent to API.
        Comment is stored locally only.

        Parameters
        ----------
        decision_id : From decision.decision_id
        was_correct : Was the decision correct?
        comment     : Optional note (stored locally only, never sent)
        """
        self._storage.save_feedback(decision_id, was_correct, comment)
        try:
            self._api.post("/feedback", {
                "decision_id": decision_id,
                "feedback": "positive" if was_correct else "negative",
            })
            return True
        except Exception:
            return False

    # ── Metrics ──────────────────────────────────────────────────

    def metrics(
        self,
        days:   int = 30,
        domain: Optional[str] = None,
    ) -> Metrics:
        """
        Get comprehensive metrics from your local decision history.

        Parameters
        ----------
        days   : Number of days to include (default: 30)
        domain : Filter by domain (optional)

        Returns
        -------
        Metrics object with accuracy, UAR, coverage, latency, etc.

        Examples
        --------
        m = client.metrics(days=7)
        print(f"Answer rate: {m.answer_rate:.1%}")
        print(f"UAR: {m.uar:.1%}")
        print(f"Avg latency: {m.avg_latency_ms:.0f}ms")
        """
        return self._analytics.compute_metrics(days=days, domain=domain)

    def metrics_by_domain(self, days: int = 30) -> Dict[str, Metrics]:
        """Get metrics broken down by domain."""
        return self._analytics.metrics_by_domain(days=days)

    def metrics_by_language(self, days: int = 30) -> Dict[str, Metrics]:
        """Get metrics broken down by detected language."""
        return self._analytics.metrics_by_language(days=days)

    def trends(self, days: int = 30, interval: str = "day") -> List[Dict]:
        """
        Get metrics over time.

        Parameters
        ----------
        days     : Number of days to include
        interval : Grouping interval: 'hour', 'day', 'week'

        Returns list of dicts with timestamp and metrics.
        """
        return self._analytics.trends(days=days, interval=interval)

    def error_analysis(self, days: int = 30) -> Dict:
        """
        Analyze decisions that received negative feedback.
        Useful for identifying patterns in incorrect decisions.
        """
        return self._analytics.error_analysis(days=days)

    def top_abstain_patterns(self, days: int = 30, top_n: int = 10) -> List[Dict]:
        """
        Return the most common query patterns that result in abstain.
        Useful for identifying gaps in your knowledge base.
        Note: returns query_type and language only, not actual query content.
        """
        return self._analytics.top_abstain_patterns(days=days, top_n=top_n)

    # ── Calibration ──────────────────────────────────────────────

    def calibrate(
        self,
        labeled_data: List[Dict],
        domain:       Optional[str] = None,
    ) -> CalibrationResult:
        """
        Find optimal thresholds for your specific corpus.

        Parameters
        ----------
        labeled_data : List of dicts with keys:
                       query, contexts, draft_answer, label (answer/clarify/abstain)
        domain       : Domain name for saving the calibrated profile

        Returns
        -------
        CalibrationResult with optimal thresholds and expected metrics.

        Examples
        --------
        data = [
            {"query": "...", "contexts": [...], "draft_answer": "...", "label": "answer"},
            ...
        ]
        result = client.calibrate(data, domain="insurance")
        print(f"Optimal tau_evidence: {result.tau_evidence}")
        print(f"Expected accuracy: {result.expected_accuracy:.1%}")
        """
        from .calibration import run_calibration
        return run_calibration(
            data=labeled_data,
            api_client=self._api,
            domain=domain,
        )

    # ── Dashboard ────────────────────────────────────────────────

    def dashboard(self, port: int = 7860, open_browser: bool = True) -> None:
        """
        Launch local analytics dashboard.

        Opens a browser with your private metrics.
        No data is sent to A2RAG servers — reads from local storage only.

        Parameters
        ----------
        port         : Local port (default: 7860)
        open_browser : Auto-open browser (default: True)

        Examples
        --------
        client.dashboard()
        # or from command line: a2rag dashboard
        """
        from .dashboard import launch_dashboard
        launch_dashboard(
            storage=self._storage,
            analytics=self._analytics,
            port=port,
            open_browser=open_browser,
        )

    # ── Account ──────────────────────────────────────────────────

    def health(self) -> Dict:
        """Check API connectivity and model status."""
        return self._api.get("/health")

    def account(self) -> Dict:
        """Get your account metrics from the API (cloud-side)."""
        return self._api.get("/metrics")

    def export(
        self,
        path:   str,
        days:   int = 30,
        format: str = "csv",
    ) -> str:
        """
        Export local decision history to file.

        Parameters
        ----------
        path   : Output file path
        days   : Number of days to export
        format : 'csv' or 'jsonl'

        Returns path to exported file.
        """
        return self._storage.export(path=path, days=days, format=format)

    def clear_local_data(self, older_than_days: int = 90) -> int:
        """
        Delete local decisions older than N days.
        Returns number of records deleted.
        """
        return self._storage.clear(older_than_days=older_than_days)
