from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass
from typing import Optional

# Underlying Go bindings module (generated by gopy)
from masktunnellib import masktunnel as gomasktunnel  # type: ignore

from ._utils import _logger, BufferZerologLogger


def _as_str(value: object, field: str) -> str:
    if isinstance(value, str):
        return value
    if value is None:
        raise TypeError(f"{field} must be str, got None")
    raise TypeError(f"{field} must be str, got {type(value).__name__}")


def _as_int(value: object, field: str) -> int:
    if isinstance(value, bool):
        raise TypeError(f"{field} must be int, got bool")
    if isinstance(value, int):
        return int(value)
    raise TypeError(f"{field} must be int, got {type(value).__name__}")


@dataclass
class ServerOptions:
    addr: str = ""
    port: str = "8080"
    user_agent: str = ""
    payload: str = ""
    upstream_proxy: str = ""
    username: str = ""
    password: str = ""
    verbose: int = 0


class Server:
    """MaskTunnel proxy server.

    Notes:
        - `start()` is blocking, so typical usage is `asyncio.to_thread(server.start)`.
        - Use `stop()` to shutdown.
    """

    def __init__(self, *, options: Optional[ServerOptions] = None, logger: Optional[logging.Logger] = None) -> None:
        if logger is None:
            logger = _logger
        self._logger = logger

        if options is None:
            opt = gomasktunnel.DefaultServerOption()
        else:
            opt = gomasktunnel.DefaultServerOption()
            opt.Addr = _as_str(options.addr, "addr")
            opt.Port = _as_str(options.port, "port")
            opt.UserAgent = _as_str(options.user_agent, "user_agent")
            opt.Payload = _as_str(options.payload, "payload")
            opt.UpstreamProxy = _as_str(options.upstream_proxy, "upstream_proxy")
            opt.Username = _as_str(options.username, "username")
            opt.Password = _as_str(options.password, "password")
            opt.Verbose = _as_int(options.verbose, "verbose")

        # Use buffer-based logger system for Go bindings
        self._managed_logger = BufferZerologLogger(logger, f"server_{id(self)}")
        opt.WithLogger(self._managed_logger.go_logger)

        self._raw = gomasktunnel.NewServerHandle(opt)

    @property
    def addr(self) -> str:
        return self._raw.Addr()

    def start(self) -> None:
        try:
            self._raw.Start()
        except RuntimeError as e:
            if "Server closed" not in str(e):
                raise

    def start_background(self) -> None:
        """Start the server in background (non-blocking).

        After this method returns, the server is ready to accept connections
        and `addr` property will return the actual listening address
        (useful when port='0' is specified to get OS-assigned port).
        """
        self._raw.StartBackground()

    async def async_start(self) -> None:
        await asyncio.to_thread(self.start)

    def stop(self) -> None:
        self._raw.Stop()

    async def async_stop(self) -> None:
        await asyncio.to_thread(self._raw.Stop)

    def reset_sessions(self) -> int:
        return int(self._raw.ResetSessions())

    def set_upstream_proxy(self, proxy_url: str) -> None:
        self._raw.SetUpstreamProxy(_as_str(proxy_url, "proxy_url"))

    def get_ca_pem(self) -> bytes:
        return bytes(self._raw.GetCAPEM() or b"")

    def close(self) -> None:
        try:
            self._raw.Close()
        except Exception:
            pass
        finally:
            if hasattr(self, "_managed_logger"):
                self._managed_logger.cleanup()

    async def async_close(self) -> None:
        await asyncio.to_thread(self.close)

    # Context manager support (with statement)
    def __enter__(self) -> "Server":
        self.start_background()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self.close()

    # Async context manager support (async with statement)
    async def __aenter__(self) -> "Server":
        self.start_background()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        await self.async_close()
