"""Thread-safe in-memory storage for extracted API collections."""

import threading
from dataclasses import dataclass, field
from datetime import datetime, timezone
from uuid import uuid4

from maeris_mcp.types.api import APISummary, ExtractedAPI, ExtractedAPICollection, StoredAPIList


@dataclass
class CreateApisForSession:
    """Active session for LLM-guided create_apis_for workflow."""

    session_id: str
    functionality: str
    target_path: str
    scan_root: str
    base_for_rel: str
    file_list: list[str]
    status: str = "scanning"  # "scanning" | "review" | "completed"
    apis: list[dict] = field(default_factory=list)
    environments: list[dict] = field(default_factory=list)
    api_fingerprints: set[str] = field(default_factory=set)
    excluded_api_names: set[str] = field(default_factory=set)
    excluded_env_names: set[str] = field(default_factory=set)
    excluded_env_fields: set[tuple[str, str]] = field(default_factory=set)
    flows: list[dict] = field(default_factory=list)
    excluded_flow_names: set[str] = field(default_factory=set)
    excluded_flow_steps: set[tuple[str, str]] = field(default_factory=set)  # (flow_name, api_name)
    feedback_history: list[str] = field(default_factory=list)
    existing_env_matches: list[dict] = field(default_factory=list)
    created_at: str = ""


@dataclass
class ApiScanSession:
    """Active scan session for LLM-guided API extraction."""

    scan_id: str
    project_name: str
    target_path: str
    scan_root: str
    base_for_rel: str
    file_list: list[str]
    apis: list[ExtractedAPI] = field(default_factory=list)
    http_clients: set[str] = field(default_factory=set)
    api_fingerprints: set[str] = field(default_factory=set)
    created_at: str = ""
    status: str = "in_progress"    # "in_progress" | "completed"
    storage_id: str | None = None  # set after finalization


class APIStore:
    """Thread-safe storage for extracted API collections."""

    def __init__(self) -> None:
        self._lock = threading.RLock()
        self._api_lists: dict[str, StoredAPIList] = {}
        self._sessions: dict[str, ApiScanSession] = {}
        self._for_sessions: dict[str, CreateApisForSession] = {}

    def store(self, name: str, collection: ExtractedAPICollection) -> str:
        """Store an API collection and return its ID."""
        with self._lock:
            storage_id = str(uuid4())
            now = datetime.now(timezone.utc).isoformat()
            self._api_lists[storage_id] = StoredAPIList(
                id=storage_id,
                name=name,
                collection=collection,
                stored_at=now,
                last_accessed=now,
            )
            return storage_id

    def get(self, storage_id: str) -> StoredAPIList | None:
        """Retrieve an API collection by ID."""
        with self._lock:
            if api_list := self._api_lists.get(storage_id):
                api_list.last_accessed = datetime.now(timezone.utc).isoformat()
                return api_list
            return None

    def get_all(self) -> list[StoredAPIList]:
        """Return all stored API collections."""
        with self._lock:
            return list(self._api_lists.values())

    def delete(self, storage_id: str) -> bool:
        """Delete an API collection by ID."""
        with self._lock:
            if storage_id in self._api_lists:
                del self._api_lists[storage_id]
                return True
            return False

    def clear(self) -> int:
        """Clear all stored API collections, return count deleted."""
        with self._lock:
            count = len(self._api_lists)
            self._api_lists.clear()
            return count

    def find_by_project_name(self, project_name: str) -> list[StoredAPIList]:
        """Find API lists by project name."""
        with self._lock:
            return [
                api_list
                for api_list in self._api_lists.values()
                if api_list.collection and api_list.collection.project_name == project_name
            ]

    # --- Scan session management ---

    def create_scan_session(
        self,
        project_name: str,
        target_path: str,
        scan_root: str,
        base_for_rel: str,
        file_list: list[str],
    ) -> str:
        """Create a new API scan session and return its scan_id."""
        with self._lock:
            scan_id = str(uuid4())
            self._sessions[scan_id] = ApiScanSession(
                scan_id=scan_id,
                project_name=project_name,
                target_path=target_path,
                scan_root=scan_root,
                base_for_rel=base_for_rel,
                file_list=file_list,
                created_at=datetime.now(timezone.utc).isoformat(),
            )
            return scan_id

    def get_session(self, scan_id: str) -> ApiScanSession | None:
        """Retrieve a scan session by ID."""
        with self._lock:
            return self._sessions.get(scan_id)

    def add_apis(self, scan_id: str, apis: list[ExtractedAPI]) -> dict[str, int]:
        """Add APIs to a session, deduplicating by method+endpoint fingerprint."""
        with self._lock:
            session = self._sessions.get(scan_id)
            if not session:
                return {"added": 0, "deduped": 0}
            added = 0
            deduped = 0
            for api in apis:
                fingerprint = f"{api.method}|{api.endpoint}|{api.name}"
                if fingerprint in session.api_fingerprints:
                    deduped += 1
                    continue
                session.api_fingerprints.add(fingerprint)
                session.apis.append(api)
                added += 1
            return {"added": added, "deduped": deduped}

    def add_http_clients(self, scan_id: str, clients: list[str]) -> None:
        """Merge HTTP client names into the session."""
        with self._lock:
            session = self._sessions.get(scan_id)
            if session:
                session.http_clients.update(clients)

    def finalize_session(self, scan_id: str) -> tuple[str, ExtractedAPICollection] | None:
        """Finalize a scan session: build collection, store it, and return (storage_id, collection)."""
        with self._lock:
            session = self._sessions.get(scan_id)
            if not session:
                return None

            apis = session.apis
            by_method: dict[str, int] = {}
            by_auth_type: dict[str, int] = {}
            endpoints: set[str] = set()
            for api in apis:
                by_method[api.method] = by_method.get(api.method, 0) + 1
                auth_type = api.auth.type if api.auth else "none"
                by_auth_type[str(auth_type)] = by_auth_type.get(str(auth_type), 0) + 1
                endpoints.add(api.endpoint)

            summary = APISummary(
                total_apis=len(apis),
                by_method=by_method,
                by_auth_type=by_auth_type,
                unique_endpoints=len(endpoints),
            )
            collection = ExtractedAPICollection(
                project_name=session.project_name,
                root_path=session.target_path,
                apis=apis,
                extraction_time=datetime.now(timezone.utc).isoformat(),
                files_analyzed=list(session.file_list),
                http_clients=sorted(session.http_clients) if session.http_clients else None,
                summary=summary,
            )
            storage_id = self.store(session.project_name, collection)
            session.status = "completed"
            session.storage_id = storage_id
            return storage_id, collection

    # --- CreateApisFor session management ---

    def create_for_session(
        self,
        functionality: str,
        target_path: str,
        scan_root: str,
        base_for_rel: str,
        file_list: list[str],
    ) -> str:
        """Create a new create_apis_for session and return its session_id."""
        with self._lock:
            session_id = str(uuid4())
            self._for_sessions[session_id] = CreateApisForSession(
                session_id=session_id,
                functionality=functionality,
                target_path=target_path,
                scan_root=scan_root,
                base_for_rel=base_for_rel,
                file_list=file_list,
                created_at=datetime.now(timezone.utc).isoformat(),
            )
            return session_id

    def get_for_session(self, session_id: str) -> CreateApisForSession | None:
        """Retrieve a create_apis_for session by ID."""
        with self._lock:
            return self._for_sessions.get(session_id)

    def add_for_apis(
        self,
        session_id: str,
        apis: list[dict],
    ) -> dict[str, int]:
        """Add API dicts to a create_apis_for session, deduplicating by method+url+name."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return {"added": 0, "deduped": 0}
            added = 0
            deduped = 0
            for api in apis:
                method = (api.get("method") or "").upper()
                url = api.get("url") or api.get("endpoint") or ""
                name = api.get("name") or ""
                fingerprint = f"{method}|{url}|{name}"
                if fingerprint in session.api_fingerprints:
                    deduped += 1
                    continue
                session.api_fingerprints.add(fingerprint)
                session.apis.append(api)
                added += 1
            return {"added": added, "deduped": deduped}

    def finalize_for_session(
        self,
        session_id: str,
        environments: list[dict],
        flows: list[dict] | None = None,
    ) -> CreateApisForSession | None:
        """Transition a create_apis_for session from scanning to review."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return None
            session.environments = environments or []
            session.flows = flows or []
            session.status = "review"
            return session

    def apply_for_toggles(
        self,
        session_id: str,
        exclude_apis: list[str] | None = None,
        include_apis: list[str] | None = None,
        exclude_flows: list[str] | None = None,
        include_flows: list[str] | None = None,
        exclude_flow_steps: list[dict] | None = None,
        include_flow_steps: list[dict] | None = None,
        exclude_environments: list[str] | None = None,
        include_environments: list[str] | None = None,
        exclude_env_fields: list[dict] | None = None,
        include_env_fields: list[dict] | None = None,
    ) -> tuple[list[str], bool]:
        """Toggle inclusion state for APIs, flows, environments, and env fields."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return [], False

            changes: list[str] = []

            if exclude_apis:
                for name in exclude_apis:
                    if name not in session.excluded_api_names:
                        session.excluded_api_names.add(name)
                        changes.append(f"Deselected API: {name}")

            if include_apis:
                for name in include_apis:
                    if name in session.excluded_api_names:
                        session.excluded_api_names.discard(name)
                        changes.append(f"Re-selected API: {name}")

            if exclude_flows:
                for name in exclude_flows:
                    if name not in session.excluded_flow_names:
                        session.excluded_flow_names.add(name)
                        changes.append(f"Deselected flow: {name}")

            if include_flows:
                for name in include_flows:
                    if name in session.excluded_flow_names:
                        session.excluded_flow_names.discard(name)
                        changes.append(f"Re-selected flow: {name}")

            if exclude_flow_steps:
                for step in exclude_flow_steps:
                    if isinstance(step, dict):
                        flow_name = step.get("flow_name", "")
                        api_name = step.get("api_name", "")
                        if flow_name and api_name:
                            key = (flow_name, api_name)
                            if key not in session.excluded_flow_steps:
                                session.excluded_flow_steps.add(key)
                                changes.append(f"Deselected step {api_name} from {flow_name}")

            if include_flow_steps:
                for step in include_flow_steps:
                    if isinstance(step, dict):
                        flow_name = step.get("flow_name", "")
                        api_name = step.get("api_name", "")
                        if flow_name and api_name:
                            key = (flow_name, api_name)
                            if key in session.excluded_flow_steps:
                                session.excluded_flow_steps.discard(key)
                                changes.append(f"Re-selected step {api_name} from {flow_name}")

            if exclude_environments:
                for name in exclude_environments:
                    if name not in session.excluded_env_names:
                        session.excluded_env_names.add(name)
                        changes.append(f"Deselected environment: {name}")

            if include_environments:
                for name in include_environments:
                    if name in session.excluded_env_names:
                        session.excluded_env_names.discard(name)
                        changes.append(f"Re-selected environment: {name}")

            if exclude_env_fields:
                for item in exclude_env_fields:
                    if isinstance(item, dict):
                        env_name = item.get("env_name", "")
                        field_name = item.get("field_name", "")
                        if env_name and field_name:
                            key = (env_name, field_name)
                            if key not in session.excluded_env_fields:
                                session.excluded_env_fields.add(key)
                                changes.append(f"Deselected env field: {env_name}.{field_name}")

            if include_env_fields:
                for item in include_env_fields:
                    if isinstance(item, dict):
                        env_name = item.get("env_name", "")
                        field_name = item.get("field_name", "")
                        if env_name and field_name:
                            key = (env_name, field_name)
                            if key in session.excluded_env_fields:
                                session.excluded_env_fields.discard(key)
                                changes.append(f"Re-selected env field: {env_name}.{field_name}")

            return changes, True

    def set_for_env_field_values(
        self,
        session_id: str,
        values: list[dict],
    ) -> tuple[list[str], bool]:
        """Set actual values for specific (env_name, field_name) pairs."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return [], False

            changes: list[str] = []
            for v in values:
                if not isinstance(v, dict):
                    continue
                env_name = v.get("env_name", "")
                field_name = v.get("field_name", "")
                field_value = v.get("field_value", "")
                if not env_name or not field_name:
                    continue
                for env in session.environments:
                    if env.get("name") == env_name:
                        for f in env.get("fields") or []:
                            if f.get("field_name") == field_name:
                                f["field_value"] = field_value
                                changes.append(f"Set {env_name}.{field_name} = {field_value!r}")

            return changes, True

    def update_for_review(
        self,
        session_id: str,
        updated_apis: list[dict] | None,
        updated_environments: list[dict] | None,
        updated_flows: list[dict] | None = None,
        feedback: str = "",
    ) -> tuple[list[str], bool]:
        """Apply content updates during review phase."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return [], False

            changes: list[str] = []

            if updated_apis is not None:
                old_names = {a.get("name") for a in session.apis}
                new_names = {a.get("name") for a in updated_apis}
                for name in sorted(old_names - new_names):
                    changes.append(f"Removed API: {name}")
                for name in sorted(new_names - old_names):
                    changes.append(f"Added API: {name}")
                old_by_name = {a.get("name"): a for a in session.apis}
                new_by_name = {a.get("name"): a for a in updated_apis}
                for name in sorted(old_names & new_names):
                    if old_by_name.get(name) != new_by_name.get(name):
                        changes.append(f"Updated API: {name}")
                session.apis = updated_apis
                session.api_fingerprints = set()
                for api in updated_apis:
                    method = (api.get("method") or "").upper()
                    url = api.get("url") or api.get("endpoint") or ""
                    name = api.get("name") or ""
                    session.api_fingerprints.add(f"{method}|{url}|{name}")

            if updated_flows is not None:
                old_flow_names = {f.get("name") for f in session.flows}
                new_flow_names = {f.get("name") for f in updated_flows}
                for name in sorted(old_flow_names - new_flow_names):
                    changes.append(f"Removed flow: {name}")
                for name in sorted(new_flow_names - old_flow_names):
                    changes.append(f"Added flow: {name}")
                old_flows_by_name = {f.get("name"): f for f in session.flows}
                new_flows_by_name = {f.get("name"): f for f in updated_flows}
                for name in sorted(old_flow_names & new_flow_names):
                    if old_flows_by_name.get(name) != new_flows_by_name.get(name):
                        changes.append(f"Updated flow: {name}")
                session.flows = updated_flows

            if updated_environments is not None:
                old_env_names = {e.get("name") for e in session.environments}
                new_env_names = {e.get("name") for e in updated_environments}
                for name in sorted(old_env_names - new_env_names):
                    changes.append(f"Removed environment: {name}")
                for name in sorted(new_env_names - old_env_names):
                    changes.append(f"Added environment: {name}")
                session.environments = updated_environments

            if feedback:
                session.feedback_history.append(feedback)

            if not changes:
                changes.append("No changes detected")

            return changes, True

    def mark_for_completed(self, session_id: str) -> bool:
        """Mark session as completed after saving to Maeris."""
        with self._lock:
            session = self._for_sessions.get(session_id)
            if not session:
                return False
            session.status = "completed"
            return True
