import logging
import os
import shutil
import sys
import tarfile
import argparse
from io import BytesIO
from pathlib import Path
from textwrap import dedent

import requests
import zipfile

from _utils.config_operations import read_config, initialize_logging, write_config
from _utils.string_operations import (
    dict_to_js_notation, 
    add_offset_to_string, 
    get_blank_scss_template
)
from _utils.file_operations import get_theme_path


def create_directories():
    """Create directories based on config.json if they don't exist."""
    # Create production directory
    os.makedirs(config["directories"]["build"], exist_ok=True)
    source_config = config["directories"]["source"]
    source_root = source_config["root"]
    # Create source directories if they don't exist.
    os.makedirs(source_root, exist_ok=True)
    for key, folder in source_config.items():
        if key == "root":
            continue
        path = os.path.join(source_root, folder)
        os.makedirs(path, exist_ok=True)


def download_and_install_packages():
    """Download and install Reveal.js and specified plugins."""
    # Create a 'cached' directory if it doesn't exist
    cached_dir = os.path.join(config["directories"]["source"]["root"], "cached")
    os.makedirs(cached_dir, exist_ok=True)

    # Check if reveal.js folder exists, then only download if forced
    reveal_js_dir = os.path.join(cached_dir, "reveal.js")
    if os.path.exists(reveal_js_dir):
        if not config.get("force_plugin_download", True):
            logging.info(
                'Skipping Reveal.js download.\nIf required, set config "force_plugin_download" to true.'
            )
    else:
        # Download and extract Reveal.js
        reveal_version = config["packages"]["reveal.js"]
        reveal_url = f"https://github.com/hakimel/reveal.js/archive/refs/tags/{reveal_version}.zip"
        try:
            response = requests.get(reveal_url)
            with zipfile.ZipFile(BytesIO(response.content)) as z:
                z.extractall(path=cached_dir)
            # Rename the folder
            if os.path.exists(reveal_js_dir):
                shutil.rmtree(reveal_js_dir)
            os.rename(
                os.path.join(cached_dir, f"reveal.js-{reveal_version}"),
                reveal_js_dir,
            )
            logging.info(f"Reveal.js version {reveal_version} installed successfully")
        except Exception as e:
            logging.error(f"Failed to download and extract Reveal.js: {e}")
            sys.exit(1)

    # Download and extract external plugins
    external_plugins = config["packages"]["reveal_plugins"].get("external", {})
    for plugin, details in external_plugins.items():
        version = details["version"]
        url = details.get("url")
        alias = details.get("alias")
        
        target_folder = os.path.join(cached_dir, f"{plugin}-{version}")
        if os.path.exists(target_folder) and not config.get("force_plugin_download", True):
            logging.info(f"Skipping download of {plugin}.")
            continue

        if url:
            try:
                response = requests.get(url)
                response.raise_for_status()

                file_ext = url.split(".")[-1].lower()
                if file_ext in ["gz", "tgz"]:
                    with tarfile.open(
                        fileobj=BytesIO(response.content), mode="r:gz"
                    ) as tar:
                        extracted_folder = tar.getmembers()[0].name.split("/")[0]
                        tar.extractall(path=cached_dir)
                elif file_ext == "zip":
                    with zipfile.ZipFile(BytesIO(response.content), "r") as zip_ref:
                        extracted_folder = zip_ref.namelist()[0].split("/")[0]
                        zip_ref.extractall(cached_dir)
                elif file_ext == "js":
                    extracted_folder = 'tmp_plugin'
                    os.makedirs(os.path.join(cached_dir, extracted_folder), exist_ok=True)
                    js_file_path = os.path.join(cached_dir, extracted_folder, f"{plugin}.js")
                    with open(js_file_path, 'wb') as js_file:
                        js_file.write(response.content)
                else:
                    raise ValueError(f"Unsupported file extension: {file_ext}")

                extracted_path = os.path.join(cached_dir, extracted_folder)
                if os.path.exists(target_folder):
                    shutil.rmtree(target_folder)
                if extracted_folder != f"{plugin}-{version}":
                    os.rename(extracted_path, target_folder)

                logging.info(
                    f"Successfully downloaded and installed {plugin} from {url}"
                )
            except Exception as e:
                logging.error(
                    f"Failed to download and install {plugin} from {url}: {e}"
                )
        else:
            if alias:
                plugin_url = f"https://registry.npmjs.org/{alias}/-/{alias}-{version}.tgz"
            else:
                plugin_url = f"https://registry.npmjs.org/{plugin}/-/{plugin}-{version}.tgz"
            try:
                response = requests.get(plugin_url)
                response.raise_for_status()

                with tarfile.open(
                    fileobj=BytesIO(response.content), mode="r:gz"
                ) as tar:
                    tar.extractall(path=cached_dir)
                extracted_folder = "package"

                extracted_path = os.path.join(cached_dir, extracted_folder)
                if os.path.exists(target_folder):
                    shutil.rmtree(target_folder)
                os.rename(extracted_path, target_folder)

                logging.info(f"Successfully downloaded and installed {plugin} from npm")
            except Exception as e:
                logging.error(f"Failed to download and install {plugin} from npm: {e}")

def update_theme():
    """Validate the theme specified in config.json."""
    theme_path_str = get_theme_path(config)
    theme_not_found = not theme_path_str
    theme_full_path = Path(theme_path_str)
    
    if theme_not_found:
        logging.warning(f"Theme file {theme_full_path} not found. Creating a template file.")
        theme_name = Path(config.get("theme","custom_theme"))
        theme_full_path = Path(args.root) / "custom_theme" / theme_name.with_suffix('.scss').name
        theme_full_path.parent.mkdir(parents=True, exist_ok=True)
        theme_full_path.write_text(get_blank_scss_template())
    if not theme_full_path.exists():
        logging.error(f"Theme {theme_full_path} not found. Update config.json!")
        sys.exit(1)
    
    write_config(args.root, "theme", str(theme_full_path), force=True)
    logging.info(f"Using theme: {theme_full_path}")

def create_reveal_template():
    """Generate Jinja2 templates for Reveal.js based on config.json."""
    source_obj = config["directories"]["source"]
    source_root = Path(source_obj.get("root", "source"))

    # Extract theme name from the path
    theme_folder, theme_name = os.path.split(config["theme"])
    theme_name = Path(theme_name).stem
    
    # Prepare the list of plugins
    builtin_plugins = config["packages"]["reveal_plugins"]["built_in"]
    external_plugins = config["packages"]["reveal_plugins"]["external"]

    # Create the list of built-in plugins
    all_plugins = [f"src/plugin/{plugin}/{plugin}.js" for plugin in builtin_plugins]

    # Create the list of external plugins, using alias and main if they exist
    for plugin, details in external_plugins.items():
        alias = details.get("alias", plugin)
        mainjs = details.get("main", plugin)

        if mainjs.endswith(".js"):
            mainjs = mainjs[:-3]

        all_plugins.append(f"src/plugin/{alias}/{mainjs}.js")

    # Generate plugin names for Reveal.initialize()
    builtin_plugin_names = [
        f"Reveal{plugin.capitalize()}" for plugin in builtin_plugins
    ]

    external_plugin_names = []
    for plugin, details in external_plugins.items():
        alias = details.get("alias", plugin)
        mainjs = details.get("main", alias)  # Use alias if main is not provided
        if mainjs.endswith(".js"):
            mainjs = mainjs[:-3]
        external_plugin_names.append(mainjs.capitalize())

    plugin_name_list = ", ".join(builtin_plugin_names + external_plugin_names)
    
    # Check if "highlight" is in the built_in plugins or "highlight.js" is in the external plugins
    if (
        "highlight" in config["packages"]["reveal_plugins"]["built_in"]
        or "highlight.js" in config["packages"]["reveal_plugins"]["external"]
    ):
        # Get the highlight theme path or default to "monokai"
        highlight_theme = config.get("highlight_theme", "monokai")

        # Check if the provided highlight_theme is a path and whether it has a .css extension
        theme_path = Path(highlight_theme)
        if not theme_path.suffix:
            theme_path = theme_path.with_suffix('.css')

        # Paths to check
        paths_to_check = [
            theme_path,
            source_root / "cached" / "reveal.js" / "plugin" / "highlight" / theme_path.name,
            Path.cwd() / theme_path.name
        ]

        # Find the highlight CSS file
        for path in paths_to_check:
            if path.exists():
                highlight_css = path.name
                highlight_css_path = path
                break

        if highlight_css:
            highlight_str = "\n".join(
                [
                    "<!-- highlight.js theme -->",
                    f'<link rel="stylesheet" href="./src/theme/{highlight_css}" id="highlight-theme">',
                ]
            )
            write_config(
                args.root, 
                "highlight_theme", 
                str(highlight_css_path), 
                force=True
                )
        else:
            logging.warning("Highlight theme CSS not found. Defaulting to 'monokai'.")

    # Parse config["custom_css"] array and insert the stylesheet links into the template
    custom_css = config.get("custom_css", [])
    custom_css_tags = "\n".join([f'<link rel="stylesheet" href="./src/css/{css}">' for css in custom_css])

    # Parse config["custom_scripts"] array and insert them into the template
    libraries_destination_dir = Path(config["directories"]["build"]) / config["directories"]["source"]["libraries"] / "custom_scripts"
    custom_scripts = config.get("custom_scripts", [])
    custom_script_tags = "\n".join([f'<script src="{libraries_destination_dir / script}"></script>' for script in custom_scripts])

    # Reveal.js configurations
    # Create a list of formatted key-value pairs
    reveal_config_str = dict_to_js_notation(config["reveal_configurations"])
    plugin_config_str = dict_to_js_notation(config["packages"]["reveal_plugins"]["plugin_configurations"])
    favicon_path = Path(source_obj.get("libraries", "lib")) / "favicon.png"

    # Create Reveal.js template with Jinja2 placeholders for build.py
    reveal_template = f"""
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <title>{{{{deck.title}}}}</title>
        <link rel="icon" type="image/x-icon" href="{favicon_path}">
        <link rel="stylesheet" href="./src/css/reset.css">
        <link rel="stylesheet" href="./src/css/reveal.css">
        <link rel="stylesheet" href="./src/theme/{theme_name}.css" id="theme">
        <link rel="stylesheet" href="./src/css/revealpack.css">
{add_offset_to_string(highlight_str, 8)}
        <!-- Custom CSS -->
{add_offset_to_string(custom_css_tags, 8)}
        <!-- Custom Scripts -->
{add_offset_to_string(custom_script_tags, 8)}
        <!-- Print PDF script -->
        <script>
            var link = document.createElement('link');
            link.rel = 'stylesheet';
            link.type = 'text/css';
            link.href = window.location.search.match(/print-pdf/gi) ? './src/css/print/pdf.css' : './src/css/print/paper.css';
            document.getElementsByTagName('head')[0].appendChild(link);
        </script>
        <!-- Deck CSS Injections -->
        {{% if deck.head and deck.head.styles %}}
        {{% for style in deck.head.styles %}}
        <link rel="stylesheet" href="./{config['directories']['source']['libraries']}/{{{{ style }}}}">
        {{% endfor %}}
        {{% endif %}}
        <!-- Deck Script Injections -->
        {{% if deck.head and deck.head.scripts %}}
        {{% for script in deck.head.scripts %}}
        <script src="./{config['directories']['source']['libraries']}/{{{{ script }}}}"></script>
        {{% endfor %}}
        {{% endif %}}
        <!-- Deck Raw Injections -->
        {{% if deck.head and deck.head.raw %}}
        {{% for entry in deck.head.raw %}}
        {{{{ entry }}}}
        
        {{% endfor %}}
        {{% endif %}}
    </head>
    <body>
        <div class="reveal">
            <div class="slides">
                {{% if deck.titlepage %}}
                <section id="deck-title-slide" {{%- if deck.titlepage.background -%}}{{%- for i, v in deck.titlepage.background.items() -%}} data-background-{{{{ i }}}}="{{{{ v }}}}"{{%- endfor -%}}{{%- endif -%}}>
                    <div class="title-slide">
                    {{% if deck.titlepage.image %}}
                        <div class="image" style="background-image: url({{{{ deck.titlepage.image }}}});"></div>
                    {{% endif %}}
                        <div class="headline">
                            {{% if deck.titlepage.headline %}}
                            {{% for line in deck.titlepage.headline %}}
                            {{% if loop.first %}}
                            <h2 class="r-fit-text">{{{{ line|title }}}}</h2>
                            {{% else %}}
                            <h3>{{{{ line|title }}}}</h3>
                            {{% endif %}}
                            {{% endfor %}}
                            {{% endif %}}
                        </div>
                        <div class="sub-header">
                            {{% if deck.titlepage.by %}}
                            {{% for by in deck.titlepage.by %}}
                            <p class="by">{{{{ by }}}}</p>
                            {{% endfor %}}
                            {{% endif %}}
                        </div>
                        <div class="byline">
                            {{% if deck.titlepage.byinfo %}}
                            {{% for byinfo in deck.titlepage.byinfo %}}
                            <p class="byinfo">{{{{ byinfo }}}}</p>
                            {{% endfor %}}
                            {{% endif %}}
                        </div>
                    </div>
                    {{% if deck.titlepage.notes %}}
                    <aside class="notes">
                    {{% for note in deck.titlepage.notes %}}<p>{{{{ note }}}}</p>
                    {{% endfor %}}
                    </aside>
                    {{% endif %}}
                </section>
                {{% endif %}}
                {{% set section_count = namespace(n = 0) -%}}
                {{% for slide in deck.slides %}}
                {{% if slide.sectiontitle %}}
                {{% set section_count.n = section_count.n + 1 %}}
                <section id="section-title-{{{{ section_count.n }}}}"{{% if slide.sectiontitle.image -%}}
                 data-background-image="{{{{ slide.sectiontitle.image.url }}}}"{{% for key, value in slide.sectiontitle.image.items() if key != 'url' -%}} data-background-{{{{ key }}}}="{{{{ value }}}}"{{% endfor -%}}
                 {{%- elif slide.sectiontitle.color -%}}
                 data-background-color="{{{{ slide.sectiontitle.color }}}}"
                 {{%- elif slide.sectiontitle.gradient -%}}
                 data-background-gradient="{{{{ slide.sectiontitle.gradient }}}}"
                 {{%- endif -%}}>
                    <div class="grid-wrapper">
                        <div class="section-title-content" id="section-content-{{{{ section_count.n }}}}">
                        <div class="section-number">
                            <span class="large-number">{{{{ section_count.n }}}}</span>
                        </div>
                        <div class="headlines">
                            {{% for line in slide.sectiontitle.headline %}}
                            {{% if loop.first %}}
                            <h2 class="r-fit-text">{{{{ line|title }}}}</h2>
                            {{% else %}}
                            <h3>{{{{ line|title }}}}</h3>
                            {{% endif %}}
                            {{% endfor %}}
                        </div>
                        </div>
                    </div>
                    {{{{ slide.content }}}}
                {{% else %}}
                <section {{{{ slide.attributes|to_html_attrs|safe }}}}>
                    {{{{ slide.content }}}}
                {{% endif %}}
                </section>
                {{% endfor %}}
            </div>
            {{% if deck.footer %}}
            <footer class="main-footer">
                <span>{{{{ deck.footer.left if deck.footer.left is defined else '' }}}}</span>
                <span style="text-align:center;">{{{{ deck.footer.middle if deck.footer.middle is defined else '' }}}}</span>
                <span style="text-align:right;">{{{{ deck.footer.right if deck.footer.right is defined else '' }}}}</span>
            </footer>
            {{% endif %}}
        </div>
        <script src="./src/reveal.js"></script>
{add_offset_to_string('\n'.join([f'<script src="./{plugin}"></script>' for plugin in all_plugins]), 8)}
        <script>
            Reveal.initialize({{
{ add_offset_to_string(reveal_config_str, 14) }
              plugins: [{plugin_name_list}],
{ add_offset_to_string(plugin_config_str, 14) }
            }});
        </script>
    </body>
</html>
"""

    reveal_template_path = source_root / config["reveal_template"]
    with reveal_template_path.open("w") as f:
        f.write(reveal_template)

    logging.info(f"Reveal template created at {reveal_template_path}")

def create_toc_template():
    """Create the TOC template and save it to the source root directory."""
    source_obj = config["directories"]["source"]
    favicon_path = os.path.join(source_obj.get("libraries","lib"), "favicon.png")
    toc_str = f"""
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Table of Contents</title>
            <link rel="icon" type="image/x-icon" href="{favicon_path}">
            <style>
                body {{
                    font-family: Arial, sans-serif;
                    background-color: #f4f4f4;
                    margin: 0;
                    padding: 0;
                }}
                h1 {{
                    background-color: #333;
                    color: white;
                    padding: 10px 0;
                    text-align: center;
                }}
                ul#toc-list {{
                    list-style: none;
                    padding: 20px;
                }}
                ul#toc-list li {{
                    background-color: #fff;
                    margin: 8px 0;
                    padding: 12px;
                    text-align: center;
                    border-radius: 4px;
                    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
                }}
                ul#toc-list li a {{
                    text-decoration: none;
                    color: #333;
                    font-weight: bold;
                }}
            </style>
        </head>
        <body>
            <h1>Table of Contents</h1>
            <ul id="toc-list">
                {{% for presentation in toc_links %}}
                    <li><a href="{{{{ presentation.link|safe }}}}"  target="_blank">
                    {{{{ presentation.name|title }}}}{{%- if presentation.titlepage -%}}: {{{{ presentation.titlepage|title }}}}{{% endif %}}
                    </a></li>
                {{% endfor %}}
            </ul>
        </body>
        </html>
        """
    toc_template_content = dedent(toc_str)

    toc_template_path = os.path.join(
        config["directories"]["source"]["root"], config["toc_template"]
    )
    with open(toc_template_path, "w") as f:
        f.write(toc_template_content)

    logging.info(f"TOC template created at {toc_template_path}")


def create_version_file(info, filename='version.txt'):
    version_content = f"""
VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=(1, 0, 0, 0),
    prodvers=(1, 0, 0, 0),
    mask=0x3f,
    flags=0x0,
    OS=0x4,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
  ),
  kids=[
    StringFileInfo(
      [
        StringTable(
          '040904B0',
          [StringStruct('FileDescription', '{info["project_title"]}'),
          StringStruct('FileVersion', '1.0'),
          StringStruct('InternalName', '{info["project_title"]}'),
          StringStruct('LegalCopyright', 'Copyright (c) {info["year"]} {", ".join(info["authors"])}'),
          StringStruct('OriginalFilename', '{info["short_title"]}.exe'),
          StringStruct('ProductName', '{info["project_title"]}'),
          StringStruct('ProductVersion', {info['version']})]
        )
      ]),
    VarFileInfo([VarStruct('Translation', [1033, 1200])])
  ]
)
"""
    with open(filename, 'w') as f:
        f.write(version_content)
    logging.info(f"Version Info created in: {filename}")

def main():
    initialize_logging(config)
    create_directories()
    download_and_install_packages()
    update_theme()
    create_reveal_template()
    create_toc_template()
    create_version_file(config["info"])
    
    # After initialization set force download off
    write_config(args.root,"force_plugin_download",False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Setup Reveal.js presentation environment.')
    parser.add_argument('--root', type=str, default=os.getcwd(), help='Target directory for setup')
    parser.add_argument('--force-plugin-download', '-f', action='store_true', help='Force plugin download')
    args = parser.parse_args() #global
    
    if args.force_plugin_download:
        write_config(args.root, "force_plugin_download", True, force=True)

    config = read_config(args.root)
    main()
