#!/usr/bin/env python3
"""REST client for bettercap API."""

import secrets
import string
import os
from typing import Any, Dict, List, Optional

import requests
from requests.auth import HTTPBasicAuth


class BettercapAPI:
    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 8083,
        user: str = "admin",
        password: Optional[str] = None,
        timeout: int = 10,
    ):
        self.host = host
        self.port = port
        self.user = user
        self.password = password or self._generate_password()
        self.timeout = timeout
        self._base_url = f"http://{host}:{port}/api"
        self._auth = HTTPBasicAuth(self.user, self.password)
        self._session = requests.Session()
        self._session.auth = self._auth
        self._session.headers.update({"Content-Type": "application/json"})

    @staticmethod
    def _generate_password(length: int = 32) -> str:
        alphabet = string.ascii_letters + string.digits
        return "".join(secrets.choice(alphabet) for _ in range(length))

    def _request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
        url = f"{self._base_url}/{endpoint.lstrip('/')}"
        kwargs.setdefault("timeout", self.timeout)
        try:
            resp = self._session.request(method, url, **kwargs)
            resp.raise_for_status()
            return resp
        except requests.exceptions.ConnectionError:
            raise ConnectionError(f"Cannot connect to bettercap at {self.host}:{self.port}")
        except requests.exceptions.Timeout:
            raise TimeoutError(f"Request to bettercap timed out after {self.timeout}s")

    def run(self, command: str) -> Dict[str, Any]:
        resp = self._request("POST", "session", json={"cmd": command})
        return resp.json()

    def get_session(self) -> Dict[str, Any]:
        resp = self._request("GET", "session")
        return resp.json()

    def get_events(self, since: Optional[int] = None) -> List[Dict[str, Any]]:
        params = {}
        if since is not None:
            params["n"] = since
        resp = self._request("GET", "events", params=params)
        return resp.json()

    def is_alive(self) -> bool:
        try:
            self.get_session()
            return True
        except (ConnectionError, TimeoutError, requests.exceptions.RequestException):
            return False

    def start_module(self, module: str) -> Dict[str, Any]:
        return self.run(f"{module} on")

    def stop_module(self, module: str) -> Dict[str, Any]:
        return self.run(f"{module} off")

    def set_parameter(self, param: str, value: str) -> Dict[str, Any]:
        # Validate param to prevent injection — allow alphanumeric, dots, underscores
        if not re.match(r'^[a-zA-Z0-9._-]+$', param):
            raise ValueError(f"Invalid parameter name: {param}")
        # Quote value to prevent injection
        quoted_value = repr(value)
        return self.run(f"set {param} {quoted_value}")

    def get_hosts(self) -> List[Dict[str, Any]]:
        session = self.get_session()
        return session.get("lan", {}).get("hosts", [])

    def load_caplet(self, caplet_path: str) -> Dict[str, Any]:
        # Validate caplet path — must be absolute, must exist, prevent traversal
        caplet_path = os.path.abspath(caplet_path)
        if not os.path.isfile(caplet_path):
            raise FileNotFoundError(f"Caplet not found: {caplet_path}")
        if not caplet_path.endswith(('.cap', '.caplet')):
            raise ValueError(f"Invalid caplet extension: {caplet_path}")
        # Quote path for safety
        quoted_path = repr(caplet_path)
        return self.run(f"caplet.load {quoted_path}")

    @property
    def credentials(self) -> Dict[str, str]:
        return {"user": self.user, "password": self.password}

    @property
    def api_flags(self) -> List[str]:
        return [
            "-api-rest-address", self.host,
            "-api-rest-port", str(self.port),
            "-api-rest-username", self.user,
            "-api-rest-password", self.password,
        ]
