"""Models for manifest validator."""
from __future__ import annotations

from dataclasses import dataclass, field
import json
import pathlib
from typing import Any


@dataclass
class Error:
    """Error validating an integration."""

    plugin: str
    error: str
    fixable: bool = False

    def __str__(self) -> str:
        """Represent error as string."""
        return f"[{self.plugin.upper()}] {self.error}"


@dataclass
class Config:
    """Config for the run."""

    specific_integrations: list[pathlib.Path] | None
    root: pathlib.Path
    action: str
    requirements: bool
    errors: list[Error] = field(default_factory=list)
    cache: dict[str, Any] = field(default_factory=dict)
    plugins: set[str] = field(default_factory=set)

    def add_error(self, *args: Any, **kwargs: Any) -> None:
        """Add an error."""
        self.errors.append(Error(*args, **kwargs))


@dataclass
class Brand:
    """Represent a brand in our validator."""

    @classmethod
    def load_dir(cls, path: pathlib.Path, config: Config) -> dict[str, Brand]:
        """Load all brands in a directory."""
        assert path.is_dir()
        brands: dict[str, Brand] = {}
        for fil in path.iterdir():
            brand = cls(fil)
            brand.load_brand(config)
            brands[brand.domain] = brand

        return brands

    path: pathlib.Path
    _brand: dict[str, Any] | None = None

    @property
    def brand(self) -> dict[str, Any]:
        """Guarded access to brand."""
        assert self._brand is not None, "brand has not been loaded"
        return self._brand

    @property
    def domain(self) -> str:
        """Integration domain."""
        return self.path.stem

    @property
    def name(self) -> str | None:
        """Return name of the integration."""
        return self.brand.get("name")

    @property
    def integrations(self) -> list[str]:
        """Return the sub integrations of this brand."""
        return self.brand.get("integrations", [])

    @property
    def iot_standards(self) -> list[str]:
        """Return list of supported IoT standards."""
        return self.brand.get("iot_standards", [])

    def load_brand(self, config: Config) -> None:
        """Load brand file."""
        if not self.path.is_file():
            config.add_error("model", f"Brand file {self.path} not found")
            return

        try:
            brand: dict[str, Any] = json.loads(self.path.read_text())
        except ValueError as err:
            config.add_error(
                "model", f"Brand file {self.path.name} contains invalid JSON: {err}"
            )
            return

        self._brand = brand


@dataclass
class Integration:
    """Represent an integration in our validator."""

    @classmethod
    def load_dir(cls, path: pathlib.Path) -> dict[str, Integration]:
        """Load all integrations in a directory."""
        assert path.is_dir()
        integrations: dict[str, Integration] = {}
        for fil in path.iterdir():
            if fil.is_file() or fil.name == "__pycache__":
                continue

            init = fil / "__init__.py"
            manifest = fil / "manifest.json"
            if not init.exists() and not manifest.exists():
                print(
                    f"Warning: {init} and manifest.json missing, "
                    "skipping directory. If this is your development "
                    "environment, you can safely delete this folder."
                )
                continue

            integration = cls(fil)
            integration.load_manifest()
            integrations[integration.domain] = integration

        return integrations

    path: pathlib.Path
    _manifest: dict[str, Any] | None = None
    manifest_path: pathlib.Path | None = None
    errors: list[Error] = field(default_factory=list)
    warnings: list[Error] = field(default_factory=list)
    translated_name: bool = False

    @property
    def manifest(self) -> dict[str, Any]:
        """Guarded access to manifest."""
        assert self._manifest is not None, "manifest has not been loaded"
        return self._manifest

    @property
    def domain(self) -> str:
        """Integration domain."""
        return self.path.name

    @property
    def core(self) -> bool:
        """Core integration."""
        return self.path.as_posix().startswith("homeassistant/components")

    @property
    def disabled(self) -> str | None:
        """Return if integration is disabled."""
        return self.manifest.get("disabled")

    @property
    def name(self) -> str:
        """Return name of the integration."""
        name: str = self.manifest["name"]
        return name

    @property
    def quality_scale(self) -> str | None:
        """Return quality scale of the integration."""
        return self.manifest.get("quality_scale")

    @property
    def config_flow(self) -> bool:
        """Return if the integration has a config flow."""
        return self.manifest.get("config_flow", False)

    @property
    def requirements(self) -> list[str]:
        """List of requirements."""
        return self.manifest.get("requirements", [])

    @property
    def dependencies(self) -> list[str]:
        """List of dependencies."""
        return self.manifest.get("dependencies", [])

    @property
    def supported_by(self) -> str:
        """Return the integration supported by this virtual integration."""
        return self.manifest.get("supported_by", {})

    @property
    def integration_type(self) -> str:
        """Get integration_type."""
        return self.manifest.get("integration_type", "hub")

    @property
    def iot_class(self) -> str | None:
        """Return the integration IoT Class."""
        return self.manifest.get("iot_class")

    @property
    def iot_standards(self) -> list[str]:
        """Return the IoT standard supported by this virtual integration."""
        return self.manifest.get("iot_standards", [])

    def add_error(self, *args: Any, **kwargs: Any) -> None:
        """Add an error."""
        self.errors.append(Error(*args, **kwargs))

    def add_warning(self, *args: Any, **kwargs: Any) -> None:
        """Add a warning."""
        self.warnings.append(Error(*args, **kwargs))

    def load_manifest(self) -> None:
        """Load manifest."""
        manifest_path = self.path / "manifest.json"
        if not manifest_path.is_file():
            self.add_error("model", f"Manifest file {manifest_path} not found")
            return

        try:
            manifest: dict[str, Any] = json.loads(manifest_path.read_text())
        except ValueError as err:
            self.add_error("model", f"Manifest contains invalid JSON: {err}")
            return

        self._manifest = manifest
        self.manifest_path = manifest_path
