from __future__ import annotations

import json
import os
import tempfile
import threading
from pathlib import Path
from typing import Any, Dict, List, Optional

from mockmesh.core.logger import get_logger
from mockmesh.storage.base import BaseStorage
from mockmesh.storage.utils import route_subdir, sanitize_name

_log = get_logger("storage.nosql")


_NOSQL_ROUTES: List[tuple] = [
    ("dynamodb--", "aws"),
    ("sqs_", "aws"),
    ("ssm_", "aws"),
    ("secretsmanager", "aws"),
    ("rds_", "aws"),
    ("kv_", "azure"),
    ("sb_", "azure"),
    ("azq_", "azure"),
    ("aztbl_", "azure"),
    ("eh_", "azure"),
    ("azappconfig", "azure"),
    ("cosmos--", "azure"),
    ("gcp_", "gcp"),
    ("fs_", "gcp"),
    ("pubsub_", "gcp"),
    ("mongo--", "mongodb"),
    ("redis_", "redis"),
    ("sql_table", "sql"),
]


class NoSQLStorage(BaseStorage):

    backend_type = "nosql"

    def __init__(self, root: Path) -> None:
        super().__init__(root)
        self._store_dir = root / "nosql"
        self._locks: Dict[str, threading.Lock] = {}
        self._global_lock = threading.Lock()

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def initialize(self) -> None:
        self._store_dir.mkdir(parents=True, exist_ok=True)
        self._ready = True
        _log.info("NoSQLStorage ready", extra={"mm_dir": str(self._store_dir)})

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _route_subdir(self, collection: str) -> str:
        return route_subdir(collection, _NOSQL_ROUTES)

    def _collection_path(self, collection: str) -> Path:
        safe = sanitize_name(collection)
        subdir = self._route_subdir(collection)
        if subdir:
            target = self._store_dir / subdir
            target.mkdir(parents=True, exist_ok=True)
            return target / f"{safe}.json"
        return self._store_dir / f"{safe}.json"

    def _lock_for(self, collection: str) -> threading.Lock:
        with self._global_lock:
            if collection not in self._locks:
                self._locks[collection] = threading.Lock()
            return self._locks[collection]

    def _read(self, collection: str) -> Dict[str, Any]:
        path = self._collection_path(collection)
        if not path.exists():
            return {}
        try:
            return json.loads(path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            return {}

    def _write(self, collection: str, data: Dict[str, Any]) -> None:
        path = self._collection_path(collection)
        fd, tmp = tempfile.mkstemp(dir=str(self._store_dir), suffix=".tmp")
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, default=str)
            os.replace(tmp, str(path))
        except Exception:
            try:
                os.unlink(tmp)
            except OSError:
                pass
            raise

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    def put(self, collection: str, key: str, value: Dict[str, Any]) -> None:
        lock = self._lock_for(collection)
        with lock:
            store = self._read(collection)
            store[key] = value
            self._write(collection, store)
        _log.debug("NoSQL put", extra={"mm_collection": collection, "mm_key": key})

    def get(self, collection: str, key: str) -> Optional[Dict[str, Any]]:
        return self._read(collection).get(key)

    def delete(self, collection: str, key: str) -> bool:
        lock = self._lock_for(collection)
        with lock:
            store = self._read(collection)
            if key not in store:
                return False
            del store[key]
            self._write(collection, store)
        return True

    def list(self, collection: str, prefix: str = "") -> List[Dict[str, Any]]:
        store = self._read(collection)
        return [
            {"key": k, "value": v}
            for k, v in store.items()
            if not prefix or k.startswith(prefix)
        ]

    def query(self, collection: str, **filters: Any) -> List[Dict[str, Any]]:
        all_records = self.list(collection)
        if not filters:
            return all_records

        result = []
        for record in all_records:
            v = record["value"]
            if all(_matches_filter(v.get(field), pred) for field, pred in filters.items()):
                result.append(record)
        return result

    # ------------------------------------------------------------------
    # Convenience helpers for interceptors
    # ------------------------------------------------------------------

    def list_collections(self) -> List[str]:
        return [p.stem for p in self._store_dir.rglob("*.json")]


def _matches_filter(field_value: Any, predicate: Any) -> bool:
    if isinstance(predicate, dict):
        if "$gt" in predicate:
            return field_value is not None and field_value > predicate["$gt"]
        if "$lt" in predicate:
            return field_value is not None and field_value < predicate["$lt"]
        if "$in" in predicate:
            return field_value in predicate["$in"]
        return False
    return field_value == predicate
