# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import argparse
import os
import re
from os import path
from typing import Optional
from urllib.request import Request, urlopen


class CheckWikiPage:
    """Check if the rules on the wiki page are up-to-date."""

    wiki_page_url = "https://wiki.mozilla.org/BugBot"
    github_tree_address = "https://github.com/mozilla/bugbot/blob/master/bugbot/rules/"
    rules_path = "bugbot/rules/"

    deleted_rules = {
        "fuzzing_bisection_without_regressed_by.py",  # replaced with `bisection_without_regressed_by.py`
        "severity_tracked.py",  # dropped in favor of `tracked_attention.py`
        "tracked_bad_severity.py",  # dropped in favor of `tracked_attention.py`
        "newbie_with_ni.py",
    }

    skipped_rules = {
        # Disabled rules:
        "workflow/p1.py",
        "workflow/p2.py",
        "workflow/p2_no_activity.py",
        "workflow/p3_p4_p5.py",
        # Temporary scripts:
        "survey_sec_bugs.py",  # not running by cron
        # Not user-facing rules:
        "stepstoreproduce.py",  # the autofix is currently disabled
        "triage_owner_rotations.py",
        "triage_rotations_outdated.py",
        "vacant_team_manager.py",
        "defect_with_please_or_enable.py",
        "missed_landing_comment.py",
        "meta_defect.py",
        "feature_but_type_defect.py",
        "workflow/multi_nag.py",
        "multi_nag.py",
        "multifix_regression.py",
        "several_dups.py",
        "several_votes.py",
        "several_cc.py",
        "several_comments.py",
        "several_see_also.py",
        "pdfjs_update.py",
        "leave_open_sec.py",
        "webcompat_score.py",
        # Experimental rules:
        "accessibilitybug.py",
        "performancebug.py",
    }

    def __init__(self) -> None:
        self.missed_tree: Optional[list] = None
        self.missed_wiki: Optional[list] = None

    def get_rules_on_wiki_page(self) -> set:
        """Get the list of rules on the wiki page."""
        req = Request(self.wiki_page_url)

        # When running on GitHub Actions, we need to add the token to the request
        # to access the wiki page. Otherwise, we get a 403 error.
        wiki_token = os.environ.get("WIKI_TOKEN_GHA")
        if wiki_token:
            req.add_header("bugbotgha", wiki_token)

        with urlopen(req) as resp:
            wiki_page_content = resp.read().decode("utf-8")

        pat = re.compile(rf"""['"]{re.escape(self.github_tree_address)}(.*)['"]""")
        rules = pat.findall(wiki_page_content)

        if not rules:
            raise Exception(f"No rules found on the wiki page {self.wiki_page_url}")

        return set(rules)

    def get_rules_in_the_tree(self) -> set:
        """Get the list of rules in the tree."""

        rules = {
            os.path.join(root, file)[len(self.rules_path) :].strip()
            for root, dirs, files in os.walk(self.rules_path)
            for file in files
            if file.endswith(".py") and file != "__init__.py"
        }

        if not rules:
            raise Exception(f"No rules found in the tree {self.rules_path}")

        return rules

    def check(self) -> None:
        """Check if the rules on the wiki page are up-to-date."""
        rules_in_the_tree = self.get_rules_in_the_tree()
        rules_on_wiki_page = self.get_rules_on_wiki_page()

        self.missed_wiki = sorted(
            rule
            for rule in rules_in_the_tree
            if rule not in rules_on_wiki_page and rule not in self.skipped_rules
        )
        self.missed_tree = sorted(
            rule
            for rule in rules_on_wiki_page
            if rule not in rules_in_the_tree
            and rule not in self.deleted_rules
            and not (
                rule.startswith("..") and path.exists(path.join(self.rules_path, rule))
            )
        )

    def print_markdown_output(self) -> None:
        """Print the output in markdown format."""

        if self.missed_wiki is None or self.missed_tree is None:
            self.check()

        if self.missed_wiki:
            print("## The following rules are not on the wiki page:")
            for rule in self.missed_wiki:
                print(f"- [{rule}]({self.github_tree_address + rule})")

        if self.missed_tree:
            print("## The following rules are not in the tree:")
            for rule in self.missed_tree:
                wiki_id = rule.replace("/", ".2F")
                print(f"- [{rule}]({self.wiki_page_url}#{wiki_id})")

    def raise_on_mismatch(self) -> None:
        """Raise an exception if the rules on the wiki page are not up-to-date."""
        if self.missed_wiki is None or self.missed_tree is None:
            self.check()

        if self.missed_wiki or self.missed_tree:
            raise Exception(
                "The rules in the tree and on the wiki page are not in sync."
            )

    def check_with_markdown_output(self):
        """Check if the rules on the wiki page are up-to-date and return a markdown output."""
        if self.missed_wiki is None or self.missed_tree is None:
            self.check()

        if self.missed_wiki:
            print("## The following rules are not on the wiki page:")
            for rule in self.missed_wiki:
                print(f"- [{rule}]({self.github_tree_address + rule})")

        if self.missed_tree:
            print("## The following rules are not in the tree:")
            for rule in self.missed_tree:
                wiki_id = rule.replace("/", ".2F")
                print(f"- [{rule}]({self.wiki_page_url}#{wiki_id})")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Check if the rules on the wiki page are up-to-date."
    )
    parser.add_argument(
        "-ci",
        "--error-on-mismatch",
        dest="error_on_mismatch",
        action="store_true",
        default=False,
        help="Throw an error if the rules are not up-to-date.",
    )
    args = parser.parse_args()

    check_wiki_page = CheckWikiPage()
    check_wiki_page.print_markdown_output()
    if args.error_on_mismatch:
        check_wiki_page.raise_on_mismatch()
