from __future__ import annotations

import asyncio
import csv
import json
from dataclasses import dataclass
from decimal import Decimal
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, urlopen

import websockets
from websockets.typing import Subprotocol

from app.tools.dzengi_probe.config import DzengiProbeConfig


@dataclass(frozen=True)
class AskPriceSample:
    sample: int
    depth_ask: str | None
    stream_ofr: str | None
    ticker_ask_price: str | None


class AskPriceEquivalenceProbe:
    def __init__(self, config: DzengiProbeConfig) -> None:
        self.config = config

    async def run(self, *, samples: int = 30) -> Path:
        output_path = (
            self.config.output_dir
            / "reports"
            / f"ask_price_equivalence_{self.config.symbol_file_name}.csv"
        )
        output_path.parent.mkdir(parents=True, exist_ok=True)

        rows: list[AskPriceSample] = []

        async with websockets.connect(
            self.config.ws_url,
            extra_headers={
                "Origin": self.config.base_url,
                "Content-Type": "application/json",
            },
            subprotocols=[Subprotocol("json")],
            ping_interval=20,
            ping_timeout=20,
            close_timeout=5,
        ) as websocket:
            await websocket.send(
                json.dumps(
                    {
                        "correlationId": "probe-market-data-subscribe",
                        "destination": "marketData.subscribe",
                        "payload": {"symbols": [self.config.symbol]},
                    }
                )
            )

            for index in range(1, samples + 1):
                stream_ofr = await self._read_next_ofr(websocket)
                depth_ask = self._get_depth_ask()
                ticker_ask_price = self._get_ticker_ask_price()

                rows.append(
                    AskPriceSample(
                        sample=index,
                        depth_ask=depth_ask,
                        stream_ofr=stream_ofr,
                        ticker_ask_price=ticker_ask_price,
                    )
                )

                await asyncio.sleep(0.2)

        self._save_csv(output_path, rows)
        return output_path

    async def _read_next_ofr(self, websocket) -> str | None:
        while True:
            raw_message = await asyncio.wait_for(
                websocket.recv(),
                timeout=self.config.timeout_seconds,
            )
            payload = json.loads(raw_message)

            if payload.get("destination") != "internal.quote":
                continue

            data = payload.get("payload")
            if not isinstance(data, dict):
                continue

            value = data.get("ofr")
            if value is None:
                continue

            return str(Decimal(str(value)))

    def _get_depth_ask(self) -> str | None:
        payload = self._get_json(
            f"/api/{self.config.api_version}/depth",
            {
                "symbol": self.config.symbol,
                "limit": self.config.depth_limit,
            },
        )

        asks = payload.get("asks")
        if not asks:
            return None

        return str(Decimal(str(asks[0][0])))

    def _get_ticker_ask_price(self) -> str | None:
        payload = self._get_json(
            f"/api/{self.config.api_version}/ticker/24hr",
            {"symbol": self.config.symbol},
        )

        value = payload.get("askPrice")
        if value is None:
            return None

        return str(Decimal(str(value)))

    def _get_json(self, path: str, params: dict[str, str]) -> dict:
        query = urlencode(params)
        url = f"{self.config.base_url}{path}?{query}"

        request = Request(
            url=url,
            method="GET",
            headers={
                "Accept": "application/json",
                "User-Agent": "dzentra-dzengi-probe/1.0",
            },
        )

        with urlopen(request, timeout=self.config.timeout_seconds) as response:
            return json.loads(response.read().decode("utf-8"))

    def _save_csv(self, path: Path, rows: list[AskPriceSample]) -> None:
        with path.open("w", encoding="utf-8", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(
                [
                    "sample",
                    "depth_ask",
                    "stream_ofr",
                    "ticker_ask_price",
                    "depth_equals_stream",
                    "ticker_equals_depth",
                    "ticker_equals_stream",
                ]
            )

            for row in rows:
                writer.writerow(
                    [
                        row.sample,
                        row.depth_ask,
                        row.stream_ofr,
                        row.ticker_ask_price,
                        row.depth_ask == row.stream_ofr,
                        row.ticker_ask_price == row.depth_ask,
                        row.ticker_ask_price == row.stream_ofr,
                    ]
                )