from __future__ import annotations
import argparse
import logging
import ssl
import urllib.parse

from snakemd import Document, Inline, MDList, Paragraph, Alert
from subete import LanguageCollection, Repo, Project
from subete.repo import logger as subete_logger


logger = logging.getLogger(__name__)


issue_url_template_base = "https://github.com/TheRenegadeCoder/sample-programs/issues/new"
issue_url_template_query = "?assignees=&labels=enhancement,{label}&template=code-snippet-request.md&title=Add+{project}+in+{language}"
naming_conventions = {
    "camel": "helloWorld",
    "hyphen": "hello-world",
    "lower": "helloworld",
    "pascal": "HelloWorld",
    "underscore": "hello_world"
}


def main():
    """
    The main drop in script for the README generation.
    """
    ssl._create_default_https_context = ssl._create_unverified_context
    args = _get_args()
    numeric_level = getattr(logging, args[1].upper(), None)
    if not isinstance(numeric_level, int):
        raise ValueError(f'Invalid log level: {args[1]}')
    logging.basicConfig(level=numeric_level)
    subete_logger.setLevel(logging.WARNING)  # Turn down the subete logging noise
    repo = Repo(sample_programs_repo_dir=args[0])
    readme_catalog = ReadMeCatalog(repo)
    for language, page in readme_catalog.pages.items():
        page.dump("README", directory=f"{args[0]}/archive/{language[0]}/{language}")


def _get_args() -> tuple:
    """
    A helper function which gets the log level from 
    the command line. Set as warning from default. 

    :return: the log level provided by the user
    """
    parser = argparse.ArgumentParser()
    parser.add_argument("path")
    parser.add_argument(
        "-log",
        "--log",
        default="warning",
        help=(
            "Provide logging level. "
            "Example --log debug', default='warning'"
        ),
    )
    options = parser.parse_args()
    return options.path, options.log


def _get_note() -> Alert:
    """
    Generates the text for the note for the README.

    :return: the note alert block in the README
    """
    note = Inline("This page is auto-generated. DO NOT EDIT!", bold=True)
    return Alert(note, Alert.Kind.NOTE)


def _get_intro_text(language: LanguageCollection) -> Paragraph:
    """
    Generates the text for the introduction of the README.

    :param language: the language to generate from the LanguageCollection
    :return: the introduction paragraph in the README
    """
    sections = [f"Welcome to Sample Programs in {language}!"]
    if language.has_docs:
        sections += [
            f" To find documentation related to the {language} code in this repo, look ",
            Inline("here", link=language.lang_docs_url()),
            "."
        ]

    return Paragraph(sections)


def _generate_program_list(language: LanguageCollection) -> list:
    """
    A helper function which generates a list of programs for the README.

    :param language: a language collection
    :return: a list of sample programs list items
    """
    list_items = list()
    for program in language:
        program_name = f"{program}"
        program_line = Paragraph([f":white_check_mark: {program_name} [Requirements]"]) \
            .insert_link(program_name, program.documentation_url()) \
            .insert_link("Requirements", program.project().requirements_url())
        if not program.has_docs():
            program_line.replace(":white_check_mark:", ":warning:") \
                .replace_link(program.documentation_url(), program.article_issue_query_url())
        list_items.append(program_line)
    return list_items


def _generate_missing_program_list(language: LanguageCollection, missing_programs: list[str]) -> list[Paragraph]:
    """
    Generates the list of programs that are missing in Markdown.

    :param language: the language with missing programs
    :param missing_programs: the list of programs missing from the language collection
    :return: the missing programs lines as Markdown strings
    """
    list_items: list[Paragraph] = list()
    missing_programs.sort(key=lambda x: x.name())
    for program in missing_programs:
        program: Project
        program_name = program.name()
        program_query = "+".join(program_name.split())
        url = issue_url_template_base + issue_url_template_query.format(
            label=program_query.lower(),
            project=program_query,
            language=urllib.parse.quote(language.name())
        )
        program_item = Paragraph([f":x: {program_name} [Requirements]"])\
            .insert_link(program_name, url)\
            .insert_link("Requirements", program.requirements_url())
        list_items.append(program_item)
    return list_items


def _generate_credit() -> Paragraph:
    p = Paragraph([
        """
        This page was generated automatically by the Sample Programs READMEs tool. 
        Find out how to support this project on Github.
        """
    ])
    p.insert_link("this project", "https://github.com/TheRenegadeCoder/sample-programs-readmes")
    return p


def _generate_program_list_header(program_count: int, total_program_count: int) -> str:
    """
    Creates the heading test for the programs list.

    :param program_count: the number of programs completed in the language
    :param total_program_count: the total number of possible programs
    :return: the heading about the program list
    """
    i = int(((program_count / total_program_count) * 4))
    emojis = [":disappointed:", ":thinking:", ":relaxed:", ":smile:", ":partying_face:"]
    return f"Sample Programs List - {program_count}/{total_program_count} {emojis[i]}"


class ReadMeCatalog:
    """
    A representation of the collection of READMEs in the Sample Programs repo.
    """

    def __init__(self, repo: Repo):
        """
        Constructs an instance of a ReadMeCatalog.
        :param repo: a repository instance
        """
        self.repo: Repo = repo
        self.pages: dict[str, Document] = dict()
        self._build_readmes()

    def _build_readme(self, language: LanguageCollection) -> None:
        """
        Creates a README page from a language collection.
        :param language: a programming language collection (e.g., Python)
        :return: None
        """
        page = Document()

        # Introduction
        page.add_heading(f"Sample Programs in {language}")
        page.add_block(_get_note())
        page.add_block(_get_intro_text(language))

        # Sample Programs Section
        program_list = _generate_program_list(language)
        page.add_heading(_generate_program_list_header(
            language.total_programs(),
            self.repo.total_approved_projects()),
            level=2
        )
        page.add_paragraph(
            f"""
            In this section, we feature a list of completed and missing programs in {language}. See above for the
            current amount of completed programs in {language}. If you see a program that is missing and would like to 
            add it, please submit an issue, so we can assign it to you. 
            """.strip()
        )

        # Completed Programs List
        page.add_heading("Completed Programs", level=3)
        page.add_paragraph(
            f"""
            Below, you'll find a list of completed code snippets in {language}. Code snippets preceded by :warning: 
            link to a GitHub issue query featuring a possible article request issue. If an article request issue 
            doesn't exist, we encourage you to create one. Meanwhile, code snippets preceded by :white_check_mark: 
            link to an existing article which provides further documentation. To see the list of approved projects, 
            check out the official Sample Programs projects list. 
            """.strip()
        ).insert_link("Sample Programs project list", "https://sampleprograms.io/projects/")
        page.add_block(MDList(program_list))

        # Missing Programs List
        if language.missing_programs_count() > 0:
            missing_programs_list = _generate_missing_program_list(language, language.missing_programs())
            page.add_heading("Missing Programs", level=3)
            page.add_paragraph(
                f"""
                The following list contains all of the approved programs that are not currently implemented in {language}.
                Click on the name of the project to easily open an issue in GitHub. Alternatively, click requirements
                to check out the description of the project. 
                """.strip()
            )
            page.add_block(MDList(missing_programs_list))

        # Testing
        page.add_heading("Testing", level=2)
        test_data = language.testinfo()
        untestable_data = language.untestable_info()
        if test_data:
            extension = test_data["folder"]["extension"]
            naming = test_data["folder"]["naming"]
            page.add_paragraph(
                f"The following list shares details about how we name all Sample Programs in {language}:"
            )
            page.add_unordered_list([
                f"Extension: {extension}",
                f"Naming Convention: {naming}"
            ])

            page.add_paragraph('For example, the "Hello World" sample would be named this:')
            page.add_unordered_list([f"{naming_conventions[naming]}{extension}"])

            page.add_paragraph(
                f"The following list shares details about what we're using to test all Sample Programs in {language}:"
            )
            page.add_unordered_list([
                f"Docker Image: {test_data['container']['image']}",
                f"Docker Tag: {test_data['container']['tag']}"
            ])
        elif untestable_data:
                page.add_paragraph(
                    f"{language} cannot be tested for the following reason:"
                )
                page.add_unordered_list([untestable_data[0]["reason"]])
        else:
            page.add_paragraph(
                """
                This language currently does not feature testing. If you'd like to help in the efforts to test all of 
                the code in this repo, consider creating a testinfo.yml file with the following information:
                """
            )
            page.add_code("folder:\n  extension:\n  naming:\n\ncontainer:\n  image:\n  tag:\n  cmd:", lang="yml")

        if not untestable_data:
            glotter = page.add_paragraph("See the Glotter2 project for more information on how to create a testinfo file.")
            glotter.insert_link("Glotter2 project", "https://github.com/rzuckerm/glotter2")

        page.add_horizontal_rule()
        page.add_block(_generate_credit())

        self.pages[language.pathlike_name()] = page

    def _build_readmes(self) -> None:
        """
        Generates all READMEs for the repo.
        :return: None
        """
        for language in self.repo:
            self._build_readme(language)


if __name__ == "__main__":
    main()
