import json
import zipfile
from dataclasses import dataclass
from typing import TYPE_CHECKING
from zipfile import ZipFile

import bs4

from .file_classes import Connections, DiagramLayout, Metadata, Settings, parse_diagram_layout
from .file_classes.theme import Theme
from .layout.layout import Layout

if TYPE_CHECKING:
    from _typeshed import StrPath

LAYOUT_ENCODING = "utf-16-le"


@dataclass
class Version:
    major: int
    minor: int

    def __str__(self) -> str:
        return f"{self.major}.{self.minor}"


class StaticFiles:
    content_types: bs4.BeautifulSoup
    connections: Connections | None
    # no datamodel, that's handled by the ssas folder
    diagram_layout: DiagramLayout
    layout: Layout
    metadata: Metadata
    version: Version
    security_bindings: bytes
    settings: Settings
    themes: dict[str, Theme]

    def __init__(  # noqa: PLR0913, PLR0917
        self,
        content_types: bs4.BeautifulSoup,
        connections: Connections | None,
        diagram_layout: DiagramLayout,
        layout: Layout,
        metadata: Metadata,
        version: Version,
        security_bindings: bytes,
        settings: Settings,
        themes: dict[str, Theme],
    ) -> None:
        self.content_types = content_types
        self.connections = connections
        self.diagram_layout = diagram_layout
        self.layout = layout
        self.metadata = metadata
        self.version = version
        self.security_bindings = security_bindings
        self.settings = settings
        self.themes = themes

    def __repr__(self) -> str:
        return f"StaticFiles(version={self.version})"

    @staticmethod
    def load_pbix(path: "StrPath") -> "StaticFiles":  # noqa: PLR0914
        """Load static report information from a PBIX file."""
        with ZipFile(path, mode="r") as zip_file:
            themes: dict[str, Theme] = {}
            theme_paths = [
                x.split("/")[-1]
                for x in zip_file.namelist()
                if x.startswith("Report/StaticResources/SharedResources/BaseThemes")
            ]
            for theme_path in theme_paths:
                theme_json = json.loads(
                    zip_file.read(
                        f"Report/StaticResources/SharedResources/BaseThemes/{theme_path}",
                    ).decode("utf-8"),
                )
                themes[theme_path] = Theme.model_validate(theme_json)

            layout_json = json.loads(zip_file.read("Report/Layout").decode(LAYOUT_ENCODING))
            layout = Layout.model_validate(layout_json)

            if "Connections" in zip_file.namelist():
                connections_json = json.loads(zip_file.read("Connections").decode("utf-8"))
                connections = Connections.model_validate(connections_json)
            else:
                connections = None

            major, minor = zip_file.read("Version").decode(LAYOUT_ENCODING).split(".")
            version = Version(int(major), int(minor))
            try:
                security_bindings = zip_file.read("SecurityBindings")
            except KeyError:  # This can occur when the PBIX was generated by pbi_core since the file is optional
                security_bindings = b""

            content_types = bs4.BeautifulSoup(
                zip_file.read("[Content_Types].xml").decode("utf-8"),
                "xml",
            )

            metadata_json = json.loads(zip_file.read("Metadata").decode(LAYOUT_ENCODING))
            metadata = Metadata.model_validate(metadata_json)

            diagram_json = json.loads(zip_file.read("DiagramLayout").decode(LAYOUT_ENCODING))
            diagram_layout = parse_diagram_layout(diagram_json)

            settings_json = json.loads(zip_file.read("Settings").decode(LAYOUT_ENCODING))
            settings = Settings.model_validate(settings_json)

        return StaticFiles(
            content_types,
            connections,
            diagram_layout,
            layout,
            metadata,
            version,
            security_bindings,
            settings,
            themes,
        )

    def save_pbix(self, path: "StrPath") -> None:
        """Saves report to file.

        We use "a" as the write mode because the ssas.save_pbix method already
        creates a PBIX with the datamodel subfile.
        """
        data: dict[str, bytes] = {}
        with zipfile.ZipFile(path, "r", compression=zipfile.ZIP_DEFLATED) as zf:
            for f in zf.namelist():
                data[f] = zf.read(f)

        with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
            if self.connections is not None:
                zf.writestr("Connections", self.connections.model_dump_json().encode("utf-8"))
            zf.writestr("Version", str(self.version).encode(LAYOUT_ENCODING))
            for name, layout in [
                ("DiagramLayout", self.diagram_layout),
                ("Report/Layout", self.layout),
                ("Settings", self.settings),
                ("Metadata", self.metadata),
            ]:
                zf.writestr(
                    name,
                    layout.model_dump_json().encode(LAYOUT_ENCODING),
                )

            for path_name, theme_info in self.themes.items():
                zf.writestr(
                    f"Report/StaticResources/SharedResources/BaseThemes/{path_name}",
                    theme_info.model_dump_json().encode("utf-8"),
                )
            zf.writestr("DataModel", data["DataModel"])
            zf.writestr("[Content_Types].xml", str(self.content_types).encode("utf8"))
