from __future__ import annotations

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

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

if TYPE_CHECKING:
    from mockmesh.storage.manager import StorageManager

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


class _CtxMixin:
    def __enter__(self):
        return self

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


class _MockQueueMessage:
    def __init__(self, msg_id: str, content: str, **kwargs: Any) -> None:
        self.id = msg_id
        self.content = content
        self.inserted_on = kwargs.get("inserted_on", time.time())
        self.expires_on = kwargs.get("expires_on", time.time() + 604800)
        self.dequeue_count = kwargs.get("dequeue_count", 0)
        self.pop_receipt = kwargs.get("pop_receipt", str(uuid.uuid4()))


class _MockQueueClient(_CtxMixin):
    def __init__(self, queue_name: str, storage: Any) -> None:
        self._queue = queue_name
        self._storage = storage

    def send_message(self, content: Any) -> _MockQueueMessage:
        q_key = f"azq_{self._queue}"
        msg_id = str(uuid.uuid4())
        body = content if isinstance(content, str) else str(content)
        pop_receipt = str(uuid.uuid4())
        now = time.time()
        self._storage.nosql.put(q_key, msg_id, {
            "MessageId": msg_id, "Body": body, "status": "queued",
            "insert_time": now, "expiry_time": now + 604800,
            "pop_receipt": pop_receipt, "dequeue_count": 0,
        })
        return _MockQueueMessage(msg_id, body, pop_receipt=pop_receipt)

    def receive_messages(self, max_messages: int = 1) -> list:
        q_key = f"azq_{self._queue}"
        records = self._storage.nosql.query(q_key, status="queued")
        result = []
        for rec in records[:max_messages]:
            val = rec["value"]
            dc = val.get("dequeue_count", 0) + 1
            pop = str(uuid.uuid4())
            self._storage.nosql.put(
                q_key, rec["key"], {**val, "status": "locked",
                                    "dequeue_count": dc, "pop_receipt": pop})
            result.append(_MockQueueMessage(
                rec["key"], val.get("Body", ""),
                inserted_on=val.get("insert_time", 0),
                expires_on=val.get("expiry_time", 0),
                dequeue_count=dc, pop_receipt=pop,
            ))
        return result

    def delete_message(self, message: Any) -> None:
        q_key = f"azq_{self._queue}"
        msg_id = message.id if hasattr(message, "id") else str(message)
        self._storage.nosql.delete(q_key, msg_id)

    def peek_messages(self, max_messages: int = 1) -> list:
        q_key = f"azq_{self._queue}"
        records = self._storage.nosql.query(q_key, status="queued")
        return [
            _MockQueueMessage(rec["key"], rec["value"].get("Body", ""),
                              inserted_on=rec["value"].get("insert_time", 0),
                              expires_on=rec["value"].get("expiry_time", 0),
                              dequeue_count=rec["value"].get("dequeue_count", 0))
            for rec in records[:max_messages]
        ]

    def clear_messages(self) -> None:
        q_key = f"azq_{self._queue}"
        for rec in self._storage.nosql.list(q_key):
            self._storage.nosql.delete(q_key, rec["key"])

    def close(self) -> None:
        pass


def handle_storage_queue(
    sm: "StorageManager", parsed: dict, request: object,
    method: str, url: str, tpl: dict,
) -> Optional[FakeAzureResponse]:
    queue_name = parsed["container"]
    q_key = f"azq_{queue_name}"

    if method == "POST" and "/messages" in url:
        raw = _get_body(request)
        msg_id = str(uuid.uuid4())
        content = raw.decode("utf-8", errors="replace") if raw else ""
        now = time.time()
        pop_receipt = str(uuid.uuid4())
        sm.nosql.put(q_key, msg_id, {
            "MessageId": msg_id, "Body": content, "status": "queued",
            "insert_time": now, "expiry_time": now + 604800,
            "pop_receipt": pop_receipt, "dequeue_count": 0,
        })
        status = tpl.get("POST", {}).get("status", 201)
        resp = {"QueueMessagesList": [{"MessageId": msg_id,
                "InsertionTime": now, "ExpirationTime": now + 604800,
                "PopReceipt": pop_receipt}]}
        return FakeAzureResponse(status, _JSON_HDR, _enc(resp))

    if method == "GET" and "/messages" in url:
        records = sm.nosql.query(q_key, status="queued")
        num = 1
        if "numofmessages=" in url:
            try:
                num = int(url.split("numofmessages=")[1].split("&")[0])
            except (ValueError, IndexError):
                num = 1
        result = []
        for rec in records[:num]:
            val = rec["value"]
            dc = val.get("dequeue_count", 0) + 1
            pop = str(uuid.uuid4())
            sm.nosql.put(q_key, rec["key"],
                         {**val, "status": "locked",
                          "dequeue_count": dc, "pop_receipt": pop})
            result.append({"MessageId": rec["key"], "MessageText": val.get("Body", ""),
                           "DequeueCount": dc, "PopReceipt": pop})
        status = tpl.get("GET", {}).get("status", 200)
        return FakeAzureResponse(status, _JSON_HDR, _enc({"QueueMessagesList": result}))

    if method == "DELETE" and "/messages/" in url:
        parts = url.rstrip("/").split("/")
        msg_id = parts[-1].split("?")[0]
        sm.nosql.delete(q_key, msg_id)
        return FakeAzureResponse(204, {}, b"")

    if method == "PUT":
        return FakeAzureResponse(201, {}, b"")

    if method == "DELETE":
        for rec in sm.nosql.list(q_key):
            sm.nosql.delete(q_key, rec["key"])
        return FakeAzureResponse(204, {}, b"")

    if method == "GET":
        count = len(sm.nosql.query(q_key, status="queued"))
        return FakeAzureResponse(200, {
            **_JSON_HDR,
            "x-ms-approximate-messages-count": str(count),
        }, b"")

    return None
