#! python3  # noqa: E265

# ############################################################################
# ########## Libraries #############
# ##################################

# standard library
import json
from pathlib import Path

# 3rd party
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.plugins import get_plugin_logger

# package
from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME
from mkdocs_rss_plugin.integrations.theme_material_base import (
    IntegrationMaterialThemeBase,
)
from mkdocs_rss_plugin.integrations.theme_material_blog_plugin import (
    IntegrationMaterialBlog,
)
from mkdocs_rss_plugin.models import MkdocsPageSubset

# conditional
try:
    from material import __version__ as material_version

except ImportError:
    material_version = None


# ############################################################################
# ########## Globals #############
# ################################

logger = get_plugin_logger(MKDOCS_LOGGER_NAME)

# ############################################################################
# ########## Logic ###############
# ################################


class IntegrationMaterialSocialCards(IntegrationMaterialThemeBase):
    # attributes
    IS_ENABLED: bool = True
    IS_SOCIAL_PLUGIN_ENABLED: bool = True
    IS_SOCIAL_PLUGIN_CARDS_ENABLED: bool = True
    CARDS_MANIFEST: dict | None = None

    def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None:
        """Integration instantiation.

        Args:
            mkdocs_config (MkDocsConfig): Mkdocs website configuration object.
            switch_force (bool, optional): option to force integration disabling. Set
                it to False to disable it even if Social Cards are enabled in Mkdocs
                configuration. Defaults to True.
        """
        # support cards for blog posts
        self.integration_material_blog = IntegrationMaterialBlog(
            mkdocs_config=mkdocs_config
        )
        # check if the integration can be enabled or not
        self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = (
            self.is_social_plugin_and_cards_enabled_mkdocs(mkdocs_config=mkdocs_config)
        )

        # if every conditions are True, enable the integration
        self.IS_ENABLED = all(
            [
                self.IS_THEME_MATERIAL,
                self.IS_SOCIAL_PLUGIN_ENABLED,
                self.IS_SOCIAL_PLUGIN_CARDS_ENABLED,
            ]
        )

        # except if the end-user wants to disable it
        if switch_force is False:
            self.IS_ENABLED = False
            logger.debug(
                "Integration with Social Cards (Material theme) is "
                "disabled in plugin's option in Mkdocs configuration."
            )

        # if enabled, save some config elements
        if self.IS_ENABLED:
            self.mkdocs_use_directory_urls = mkdocs_config.use_directory_urls
            self.mkdocs_site_url = mkdocs_config.site_url
            self.mkdocs_site_build_dir = mkdocs_config.site_dir
            self.social_cards_dir = self.get_social_cards_dir(
                mkdocs_config=mkdocs_config
            )
            self.social_cards_assets_dir = self.get_social_cards_build_dir(
                mkdocs_config=mkdocs_config
            )
            self.social_cards_cache_dir = self.get_social_cards_cache_dir(
                mkdocs_config=mkdocs_config
            )

            self.load_cache_cards_manifest()

    def is_social_plugin_enabled_mkdocs(
        self, mkdocs_config: MkDocsConfig | None = None
    ) -> bool:
        """Check if social plugin is installed and enabled.

        Args:
            mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object.

        Returns:
            bool: True if the theme material and the plugin social cards is enabled.
        """
        if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig):
            mkdocs_config = self.mkdocs_config

        if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config):
            logger.debug("Installed theme is not 'material'. Integration disabled.")
            return False

        if not mkdocs_config.plugins.get(f"{self.THEME_NAME}/social"):
            logger.debug("Material Social plugin not listed in configuration.")
            return False

        social_plugin_cfg = mkdocs_config.plugins.get(f"{self.THEME_NAME}/social")

        if not social_plugin_cfg.config.enabled:
            logger.debug("Material Social plugin is installed but disabled.")
            self.IS_SOCIAL_PLUGIN_ENABLED = False
            return False

        logger.debug("Material Social plugin is enabled in Mkdocs configuration.")
        self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
        return True

    def is_social_plugin_and_cards_enabled_mkdocs(
        self, mkdocs_config: MkDocsConfig
    ) -> bool:
        """Check if social cards plugin is enabled.

        Args:
            mkdocs_config (MkDocsConfig): Mkdocs website configuration object.

        Returns:
            bool: True if the theme material and the plugin social cards is enabled.
        """
        if not self.is_social_plugin_enabled_mkdocs(mkdocs_config=mkdocs_config):
            return False

        social_plugin_cfg = mkdocs_config.plugins.get(f"{self.THEME_NAME}/social")

        if not social_plugin_cfg.config.cards:
            logger.debug(
                "Material Social plugin is installed, present but cards are disabled."
            )
            self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = False
            return False

        logger.debug("Material Social cards are enabled in Mkdocs configuration.")
        self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
        return True

    def is_social_plugin_enabled_page(
        self, mkdocs_page: MkdocsPageSubset, fallback_value: bool = True
    ) -> bool:
        """Check if the social plugin is enabled or disabled for a specific page. Plugin
            has to be enabled in Mkdocs configuration before.

        Args:
            mkdocs_page: Mkdocs page object.
            fallback_value: fallback value. It might be the
                'plugins.social.cards.enabled' option in Mkdocs config. Defaults to True.

        Returns:
            True if the social cards are enabled for a page.
        """
        return mkdocs_page.meta.get("social", {"cards": fallback_value}).get(
            "cards", fallback_value
        )

    def load_cache_cards_manifest(self) -> dict | None:
        """Load social cards manifest if the file exists.

        Returns:
            dict | None: manifest as dict or None if the file does not exist
        """
        cache_cards_manifest = Path(self.social_cards_cache_dir).joinpath(
            "manifest.json"
        )
        if not cache_cards_manifest.is_file():
            logger.debug(
                "Material Social Cards cache manifest file not found: "
                f"{cache_cards_manifest}"
            )
            return None

        with cache_cards_manifest.open(mode="r", encoding="UTF-8") as manifest:
            self.CARDS_MANIFEST = json.load(manifest)
        logger.debug(
            f"Material Social Cards cache manifest loaded from {cache_cards_manifest}"
        )

        return self.CARDS_MANIFEST

    def get_social_cards_dir(self, mkdocs_config: MkDocsConfig) -> str:
        """Get Social Cards folder relative to Mkdocs site_dir.
        See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir

        Args:
            mkdocs_config (MkDocsConfig): Mkdocs website configuration object.

        Returns:
            str: The cards_dir if the theme material and the plugin social cards is enabled.
        """
        social_plugin_cfg = mkdocs_config.plugins.get(f"{self.THEME_NAME}/social")

        logger.debug(
            "Material Social cards folder in Mkdocs build directory: "
            f"{social_plugin_cfg.config.cards_dir}."
        )

        return social_plugin_cfg.config.cards_dir

    def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> Path:
        """Get Social Cards folder within Mkdocs site_dir.
        See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir

        Args:
            mkdocs_config (MkDocsConfig): Mkdocs website configuration object.

        Returns:
            Path: Absolute path of the assets dir if the theme material and the plugin
                  social cards is enabled.
        """
        cards_dir = self.get_social_cards_dir(mkdocs_config=mkdocs_config)

        return Path(cards_dir).resolve()

    def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> Path:
        """Get Social Cards folder within Mkdocs site_dir.
        See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir

        Args:
            mkdocs_config (MkDocsConfig): Mkdocs website configuration object.

        Returns:
            Path: The cache dir if the theme material and the plugin social cards is enabled.
        """
        social_plugin_cfg = mkdocs_config.plugins.get(f"{self.THEME_NAME}/social")

        if (
            Path(social_plugin_cfg.config.cache_dir)
            .resolve()
            .is_relative_to(Path(mkdocs_config.config_file_path).parent.resolve())
        ):
            self.social_cards_cache_dir = Path(
                social_plugin_cfg.config.cache_dir
            ).resolve()
        else:
            self.social_cards_cache_dir = (
                Path(mkdocs_config.config_file_path)
                .parent.resolve()
                .joinpath(social_plugin_cfg.config.cache_dir)
            )

        logger.debug(
            "Material Social cards cache folder: "
            f"{self.social_cards_cache_dir}. "
            f"Already exists: {self.social_cards_cache_dir.is_dir()}"
        )

        return self.social_cards_cache_dir

    def get_social_card_build_path_for_page(
        self, mkdocs_page: MkdocsPageSubset, mkdocs_site_dir: str | None = None
    ) -> Path | None:
        """Get social card path in Mkdocs build dir for a specific page.

        Args:
            mkdocs_page: Mkdocs page object.
            mkdocs_site_dir: Mkdocs build site dir. If None, the
                'class.mkdocs_site_build_dir' is used. is Defaults to None.

        Returns:
            path to the image once published
        """
        if mkdocs_site_dir is None and self.mkdocs_site_build_dir:
            mkdocs_site_dir = self.mkdocs_site_build_dir

        # if page is a blog post
        if (
            self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
            and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
        ):
            expected_built_card_path = Path(
                f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/"
                f"{Path(mkdocs_page.dest_uri).parent}.png"
            )
        else:
            expected_built_card_path = Path(
                f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/"
                f"{Path(mkdocs_page.src_uri).with_suffix('.png')}"
            )

        if expected_built_card_path.is_file():
            logger.debug(
                f"Social card file found in build folder: {expected_built_card_path}"
            )
            return expected_built_card_path
        else:
            logger.debug(
                f"Social card not found in build folder: {expected_built_card_path}"
            )
            return None

    def get_social_card_cache_path_for_page(
        self, mkdocs_page: MkdocsPageSubset
    ) -> Path | None:
        """Get social card path in social plugin cache folder for a specific page.

        The cache mechanism in stores images directly with the
        corresponding Page's path and name and keep a correspondance matrix with hashes
        in a manifest.json.

        Args:
            mkdocs_page: Mkdocs page object.

        Returns:
            path to the image in local cache folder if it exists
        """
        # if page is a blog post
        if (
            self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
            and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
        ):
            logger.debug(
                f"Looking for social card in cache for blog post: {mkdocs_page.src_uri}"
            )
            expected_cached_card_path = self.social_cards_cache_dir.joinpath(
                f"assets/images/social/{Path(mkdocs_page.dest_uri).parent}.png"
            )
        else:
            logger.debug(
                f"Looking for social card in cache for page: {mkdocs_page.src_uri}"
            )
            expected_cached_card_path = self.social_cards_cache_dir.joinpath(
                f"assets/images/social/{Path(mkdocs_page.src_uri).with_suffix('.png')}"
            )

        if expected_cached_card_path.is_file():
            logger.debug(
                f"Social card file found in cache folder: {expected_cached_card_path}"
            )
            return expected_cached_card_path
        else:
            logger.debug(
                f"Social card not found in cache folder: {expected_cached_card_path}"
            )

    def get_social_card_url_for_page(
        self,
        mkdocs_page: MkdocsPageSubset,
        mkdocs_site_url: str | None = None,
    ) -> str:
        """Get social card URL for a specific page in documentation.

        Args:
            mkdocs_page: subset of Mkdocs page object.
            mkdocs_site_url: Mkdocs site URL. If None, the
                'class.mkdocs_site_url' is used. is Defaults to None.

        Returns:
            URL to the image once published
        """
        if mkdocs_site_url is None and self.mkdocs_site_url:
            mkdocs_site_url = self.mkdocs_site_url

        # if page is a blog post
        if (
            self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
            and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
        ):
            if self.mkdocs_use_directory_urls:
                # see: https://github.com/Guts/mkdocs-rss-plugin/issues/319
                page_social_card = (
                    f"{mkdocs_site_url}{self.social_cards_dir}/"
                    f"{Path(mkdocs_page.dest_uri).parent.with_suffix('.png')}"
                )
            else:
                page_social_card = (
                    f"{mkdocs_site_url}{self.social_cards_dir}/"
                    f"{Path(mkdocs_page.dest_uri).with_suffix('.png')}"
                )
        else:
            page_social_card = (
                f"{mkdocs_site_url}{self.social_cards_dir}/"
                f"{Path(mkdocs_page.src_uri).with_suffix('.png')}"
            )

        logger.debug(f"Use social card url: {page_social_card}")

        return page_social_card
