from __future__ import annotations

import time
import uuid
from typing import TYPE_CHECKING, Any, Optional

from mockmesh.interceptors.azure.utils import _enc, _get_body, _safe_json
from mockmesh.interceptors.responses import FakeAzureResponse

if TYPE_CHECKING:
    from mockmesh.storage.manager import StorageManager

_JSON_HDR = {"Content-Type": "application/json"}
_COL = "appconfig"


def _cfg_key(key: str, label: str = "") -> str:
    return key + "|" + (label or "")


class _CtxMixin:
    def __enter__(self):
        return self

    def __exit__(self, *_: Any) -> None:
        self.close()  # type: ignore[attr-defined]


class _MockConfigurationSetting:
    def __init__(self, **kwargs: Any) -> None:
        self.key = kwargs.get("key", "")
        self.value = kwargs.get("value", "")
        self.label = kwargs.get("label", "")
        self.content_type = kwargs.get("content_type", "")
        self.etag = kwargs.get("etag", str(uuid.uuid4()))
        self.last_modified = kwargs.get("last_modified", time.time())


class _MockAzureAppConfigClient(_CtxMixin):
    def __init__(self, storage: Any) -> None:
        self._storage = storage

    def get_configuration_setting(self, key: str,
                                  label: str = None) -> _MockConfigurationSetting:
        k = _cfg_key(key, label or "")
        item = self._storage.nosql.get(_COL, k)
        if not item:
            raise Exception("ResourceNotFoundError")
        return _MockConfigurationSetting(**item)

    def set_configuration_setting(self, setting: Any) -> _MockConfigurationSetting:
        key = setting.key if hasattr(setting, "key") else setting.get("key", "")
        label = (setting.label if hasattr(setting, "label")
                 else setting.get("label", ""))
        value = (setting.value if hasattr(setting, "value")
                 else setting.get("value", ""))
        ct = (setting.content_type if hasattr(setting, "content_type")
              else setting.get("content_type", ""))
        k = _cfg_key(key, label or "")
        etag = str(uuid.uuid4())
        now = time.time()
        stored = {"key": key, "value": value, "label": label or "",
                  "content_type": ct, "etag": etag, "last_modified": now}
        self._storage.nosql.put(_COL, k, stored)
        return _MockConfigurationSetting(**stored)

    def delete_configuration_setting(self, key: str,
                                     label: str = None) -> None:
        k = _cfg_key(key, label or "")
        self._storage.nosql.delete(_COL, k)

    def list_configuration_settings(self, key_filter: str = None,
                                    label_filter: str = None) -> list:
        items = self._storage.nosql.list(_COL)
        result = []
        for rec in items:
            val = rec["value"]
            if key_filter and not val.get("key", "").startswith(
                    key_filter.rstrip("*")):
                continue
            if label_filter and val.get("label", "") != label_filter:
                continue
            result.append(_MockConfigurationSetting(**val))
        return result

    def close(self) -> None:
        pass


def handle_appconfig(
    sm: "StorageManager", parsed: dict, request: object,
    method: str, url: str, tpl: dict,
) -> Optional[FakeAzureResponse]:
    body_data = _safe_json(_get_body(request))

    label = ""
    if "label=" in url:
        try:
            label = url.split("label=")[1].split("&")[0]
        except (IndexError, ValueError):
            label = ""

    if "/locks/" in url:
        parts = url.split("/locks/")
        key_name = parts[1].split("?")[0] if len(parts) > 1 else ""
        k = _cfg_key(key_name, label)
        item = sm.nosql.get(_COL, k)

        if method == "GET":
            locked = (item or {}).get("locked", False)
            return FakeAzureResponse(200, _JSON_HDR,
                                     _enc({"key": key_name, "locked": locked}))
        if method == "PUT":
            if item:
                sm.nosql.put(_COL, k, {**item, "locked": True})
            return FakeAzureResponse(200, _JSON_HDR,
                                     _enc({"key": key_name, "locked": True}))
        if method == "DELETE":
            if item:
                sm.nosql.put(_COL, k, {**item, "locked": False})
            return FakeAzureResponse(200, _JSON_HDR,
                                     _enc({"key": key_name, "locked": False}))

    if "/kv/" in url:
        parts = url.split("/kv/")
        key_name = parts[1].split("?")[0] if len(parts) > 1 else ""
        k = _cfg_key(key_name, label)

        if method == "GET":
            item = sm.nosql.get(_COL, k)
            if item:
                return FakeAzureResponse(200, _JSON_HDR, _enc(item))
            err = {"error": {"code": "KeyNotFound",
                             "message": f"Key '{key_name}' not found"}}
            return FakeAzureResponse(404, _JSON_HDR, _enc(err))

        if method == "PUT":
            etag = str(uuid.uuid4())
            now = time.time()
            stored = {
                "key": key_name, "label": label,
                "value": body_data.get("value", ""),
                "content_type": body_data.get("content_type", ""),
                "etag": etag, "last_modified": now,
            }
            sm.nosql.put(_COL, k, stored)
            return FakeAzureResponse(200, _JSON_HDR, _enc(stored))

        if method == "DELETE":
            sm.nosql.delete(_COL, k)
            return FakeAzureResponse(204, {}, b"")

    if url.rstrip("/").endswith("/kv") or "/kv?" in url:
        if method == "GET":
            items = sm.nosql.list(_COL)
            result = []
            filt = ""
            if "$filter=" in url:
                try:
                    filt = url.split("$filter=")[1].split("&")[0]
                except (IndexError, ValueError):
                    filt = ""
            for rec in items:
                val = rec["value"]
                if filt and val.get("key", "") and filt not in val["key"]:
                    continue
                result.append(val)
            return FakeAzureResponse(200, _JSON_HDR, _enc({"items": result}))

    return None
