from __future__ import annotations

import re
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"}

_ENTITY_RE = re.compile(
    r"\(PartitionKey='([^']*)',\s*RowKey='([^']*)'\)")
_TABLE_DEL_RE = re.compile(r"/Tables\('([^']*)'\)")


def _entity_key(pk: str, rk: str) -> str:
    return pk + "|" + rk


class _CtxMixin:
    def __enter__(self):
        return self

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


class _MockTableClient(_CtxMixin):
    def __init__(self, table_name: str, storage: Any) -> None:
        self._table = table_name
        self._storage = storage

    def _col(self) -> str:
        return f"aztbl_{self._table}"

    def create_entity(self, entity: dict) -> dict:
        pk = entity["PartitionKey"]
        rk = entity["RowKey"]
        key = _entity_key(pk, rk)
        self._storage.nosql.put(self._col(), key, entity)
        return entity

    def get_entity(self, partition_key: str, row_key: str) -> dict:
        key = _entity_key(partition_key, row_key)
        item = self._storage.nosql.get(self._col(), key)
        if not item:
            raise Exception("ResourceNotFoundError")
        return item

    def upsert_entity(self, entity: dict) -> dict:
        pk = entity["PartitionKey"]
        rk = entity["RowKey"]
        key = _entity_key(pk, rk)
        self._storage.nosql.put(self._col(), key, entity)
        return entity

    def delete_entity(self, partition_key: str, row_key: str) -> None:
        key = _entity_key(partition_key, row_key)
        self._storage.nosql.delete(self._col(), key)

    def query_entities(self, filter: str = None) -> list:  # noqa: A002
        return [r["value"] for r in self._storage.nosql.list(self._col())]

    def list_entities(self) -> list:
        return [r["value"] for r in self._storage.nosql.list(self._col())]

    def close(self) -> None:
        pass


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

    def create_table(self, name: str) -> _MockTableClient:
        self._storage.nosql.put("aztbl__tables", name, {"TableName": name})
        return _MockTableClient(name, self._storage)

    def delete_table(self, name: str) -> None:
        self._storage.nosql.delete("aztbl__tables", name)

    def list_tables(self) -> list:
        return [r["value"] for r in self._storage.nosql.list("aztbl__tables")]

    def get_table_client(self, name: str) -> _MockTableClient:
        return _MockTableClient(name, self._storage)

    def close(self) -> None:
        pass


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

    m_del_table = _TABLE_DEL_RE.search(url)
    if method == "DELETE" and m_del_table:
        tbl = m_del_table.group(1)
        sm.nosql.delete("aztbl__tables", tbl)
        return FakeAzureResponse(204, {}, b"")

    if url.rstrip("/").endswith("/Tables") or url.rstrip("/").endswith("/Tables()"):
        if method == "POST":
            tbl_name = body_data.get("TableName", "unknown")
            sm.nosql.put("aztbl__tables", tbl_name, {"TableName": tbl_name})
            return FakeAzureResponse(201, _JSON_HDR, _enc({"TableName": tbl_name}))
        if method == "GET":
            items = sm.nosql.list("aztbl__tables")
            return FakeAzureResponse(200, _JSON_HDR,
                                     _enc({"value": [r["value"] for r in items]}))

    m_ent = _ENTITY_RE.search(url)
    table_name = parsed.get("container", "")
    col = f"aztbl_{table_name}"

    if m_ent:
        pk, rk = m_ent.group(1), m_ent.group(2)
        key = _entity_key(pk, rk)

        if method == "GET":
            item = sm.nosql.get(col, key)
            if item:
                return FakeAzureResponse(200, _JSON_HDR, _enc(item))
            err = {"odata.error": {"code": "ResourceNotFound",
                                   "message": {"value": "The specified resource does not exist."}}}
            return FakeAzureResponse(404, _JSON_HDR, _enc(err))

        if method in ("PUT", "MERGE", "PATCH"):
            existing = sm.nosql.get(col, key) or {}
            if method == "PUT":
                entity = {**body_data, "PartitionKey": pk, "RowKey": rk}
            else:
                entity = {**existing, **body_data, "PartitionKey": pk, "RowKey": rk}
            sm.nosql.put(col, key, entity)
            return FakeAzureResponse(204, {}, b"")

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

    if method == "POST" and table_name:
        pk = body_data.get("PartitionKey", str(uuid.uuid4()))
        rk = body_data.get("RowKey", str(uuid.uuid4()))
        key = _entity_key(pk, rk)
        entity = {**body_data, "PartitionKey": pk, "RowKey": rk}
        sm.nosql.put(col, key, entity)
        return FakeAzureResponse(201, _JSON_HDR, _enc(entity))

    if method == "GET" and table_name:
        items = sm.nosql.list(col)
        return FakeAzureResponse(200, _JSON_HDR,
                                 _enc({"value": [r["value"] for r in items]}))

    return None
