from dataclasses import dataclass
from os import getenv
from pathlib import Path
from typing import Literal

import msgspec
import structlog
from sqlalchemy import URL

from csirtmu.core.dataclass_config_loader import load_dataclass_config
from csirtmu.self_service_backend.input_structs import (
    FormStepInput,
    LinkStepInput,
    NetboxSearchStepInput,
    OutlineSearchStepInput,
    TextStepInput,
)

BASE_DIR = Path(__file__).parent.parent.parent
ENV_PREFIX = "CSIRTMU_SELF_SERVICE_BACKEND"

UsernameEncoding = Literal["base64", "urlencode"] | None


logger = structlog.get_logger(__name__)


@dataclass
class RTConfig:
    url: str
    token: str | None
    verify_cert: bool = True

    @property
    def url_slash(self) -> str:
        return self.url + ("" if self.url.endswith("/") else "/")

    @property
    def host(self) -> str:
        return self.url_slash + "REST/2.0/"

    @property
    def display_url(self) -> str:
        return self.url_slash + "Ticket/Display.html?id="


@dataclass(unsafe_hash=True)
class NetboxConfig:
    url: str
    token: str
    link_url: str

    @property
    def auth_headers(self) -> dict[str, str]:
        return {"Authorization": f"Token {self.token}"}

    @property
    def graphql_url(self) -> str:
        url_ = self.url
        url_ = url_.removesuffix("/")  # pragma: no cover
        return url_ if url_.endswith("/graphql") else f"{url_}/graphql/"


@dataclass(unsafe_hash=True)
class OutlineConfig:
    url: str
    token: str
    link_url: str

    @property
    def base_url(self) -> str:
        return self.url + ("" if self.url.endswith("/") else "/")

    @property
    def documents_search_url(self) -> str:
        return self.base_url + "api/documents.search"

    @property
    def auth_headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer " + self.token}


@dataclass(unsafe_hash=True)
class DatabaseConfig:
    username: str
    password: str
    host: str
    port: int
    database: str
    timeout: float = 30.0

    @property
    def engine_url(self) -> URL:
        return URL.create(
            "postgresql+psycopg",
            username=self.username,
            password=self.password,
            host=self.host,
            port=self.port,
            database=self.database,
        )


@dataclass(unsafe_hash=True)
class LoadConfig:
    database: DatabaseConfig
    rt: RTConfig
    netbox: NetboxConfig
    outline: OutlineConfig
    load_steps: bool = False
    step_config_path: str = str(Path(BASE_DIR / "config/steps"))


@dataclass(unsafe_hash=True, kw_only=True)
class Config(LoadConfig):
    steps: list[TextStepInput | LinkStepInput | FormStepInput | NetboxSearchStepInput | OutlineSearchStepInput]


class AppConfig:
    _config: Config | None = None

    @classmethod
    def load_steps(
        cls, path: str
    ) -> list[TextStepInput | LinkStepInput | FormStepInput | NetboxSearchStepInput | OutlineSearchStepInput]:
        steps: list[TextStepInput | LinkStepInput | FormStepInput | NetboxSearchStepInput | OutlineSearchStepInput] = []
        for file in [f for f in Path(path).glob("*.yaml") if f.is_file()]:
            with file.open("r") as f:
                try:
                    steps.append(
                        msgspec.yaml.decode(
                            f.read(),
                            type=TextStepInput
                            | LinkStepInput
                            | FormStepInput
                            | NetboxSearchStepInput
                            | OutlineSearchStepInput,
                        )
                    )
                except msgspec.ValidationError:
                    logger.exception("Unable to parse step config.")
                    raise
        steps.sort(key=lambda x: x.id or 1)
        return steps

    @classmethod
    def get(cls, config_path: Path | None = None) -> Config:
        if cls._config is None:
            if config_path is None:  # pragma: no cover
                config_path = Path(
                    getenv(
                        ENV_PREFIX,
                        (BASE_DIR / "config/config.yaml").resolve(),
                    )
                )
            config = load_dataclass_config(config_path, LoadConfig, ENV_PREFIX)
            cls._config = Config(
                database=config.database,
                rt=config.rt,
                netbox=config.netbox,
                outline=config.outline,
                load_steps=config.load_steps,
                steps=cls.load_steps(config.step_config_path) if config.load_steps else [],
            )
        return cls._config
