# 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 io
import re
import sys
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path

from mozlint import result
from mozlint.pathutils import expand_exclusions

TOPSRCDIR = Path(__file__).parent.parent.parent.parent
sys.path.insert(
    0, str(TOPSRCDIR / "toolkit/components/glean/build_scripts/glean_parser_ext")
)
sys.path.insert(0, str(TOPSRCDIR / "toolkit/components/glean"))

from metrics_index import tags_yamls
from run_glean_parser import ParserError, get_parser_options, parse_with_options

METRIC_NAME_PATTERN = re.compile(r"\.([^:]+):")
COMMON_PREFIX_PATTERN = re.compile(r"COMMON_PREFIX: ([^:]+):")

FENIX_PATTERN = re.compile(r"mobile/android/fenix/")
FOCUS_PATTERN = re.compile(r"mobile/android/focus")


def lint(paths, config, fix=None, **lintargs):
    """Lint Glean metrics.yaml and pings.yaml files using glean_parser's linter."""

    results = []

    groups = {
        "fenix": {
            "metrics": [],
            "pings": [],
            "tags": [TOPSRCDIR / "mobile/android/fenix/app/tags.yaml"],
        },
        "focus": {
            "metrics": [],
            "pings": [],
            "tags": [TOPSRCDIR / "mobile/android/focus-android/app/tags.yaml"],
        },
        "default": {
            "metrics": [],
            "pings": [],
            "tags": [TOPSRCDIR / tag for tag in tags_yamls],
        },
    }

    for p in expand_exclusions(paths, config, lintargs["root"]):
        file_path = Path(p)

        if file_path.name.endswith("metrics.yaml"):
            path_str = file_path.as_posix()
            if FENIX_PATTERN.search(path_str):
                groups["fenix"]["metrics"].append(file_path)
            elif FOCUS_PATTERN.search(path_str):
                groups["focus"]["metrics"].append(file_path)
            else:
                groups["default"]["metrics"].append(file_path)
        elif file_path.name.endswith("pings.yaml"):
            path_str = file_path.as_posix()
            if FENIX_PATTERN.search(path_str):
                groups["fenix"]["pings"].append(file_path)
            elif FOCUS_PATTERN.search(path_str):
                groups["focus"]["pings"].append(file_path)
            else:
                groups["default"]["pings"].append(file_path)

    if not any(group["metrics"] or group["pings"] for group in groups.values()):
        return {"results": results, "fixed": 0}

    # This only impacts "EXPIRED" rules which we're ignoring anyway
    irrelevant_version_number = "500.0"
    options = get_parser_options(irrelevant_version_number, False)

    for group_name, group_data in groups.items():
        metrics_files = group_data["metrics"]
        pings_files = group_data["pings"]
        tags_files = group_data["tags"]

        if not metrics_files and not pings_files:
            continue

        error_stream = io.StringIO()
        group_files = metrics_files + pings_files

        input_files = []
        if metrics_files:
            input_files.extend(metrics_files)
            input_files.extend(tags_files)
        input_files.extend(pings_files)

        with redirect_stdout(io.StringIO()), redirect_stderr(error_stream):
            try:
                parse_with_options(input_files, options, file=error_stream)
            except ParserError:
                for line in error_stream.getvalue().splitlines():
                    stripped_line = line.strip()

                    if stripped_line.startswith(("WARNING:", "ERROR:")):
                        message = stripped_line.removeprefix("WARNING: ").removeprefix(
                            "ERROR: "
                        )

                        # Try to find line number for COMMON_PREFIX errors (category level)
                        common_prefix_match = COMMON_PREFIX_PATTERN.search(message)
                        if common_prefix_match:
                            lineno, file_path = find_yaml_line_in_group(
                                f"{common_prefix_match.group(1)}:", group_files
                            )
                        else:
                            # Try to find line number for metric-specific errors
                            match = METRIC_NAME_PATTERN.search(message)
                            if match:
                                metric_name = (
                                    match.group(1).split(".")[-1]
                                    if "." in match.group(1)
                                    else match.group(1)
                                )
                                lineno, file_path = find_yaml_line_in_group(
                                    f"  {metric_name}:", group_files
                                )
                            else:
                                lineno, file_path = None, (
                                    group_files[0] if group_files else None
                                )

                        if file_path:
                            results.append(
                                result.from_config(
                                    config,
                                    path=str(file_path),
                                    message=message,
                                    level="error",
                                    lineno=lineno,
                                )
                            )

    return {"results": results, "fixed": 0}


def find_yaml_line_in_group(search_pattern, files):
    """Find a pattern in a group of files and return (line_number, file_path)."""
    if not search_pattern:
        return None, None

    for file_path in files:
        for i, line in enumerate(file_path.read_text(encoding="utf-8").splitlines(), 1):
            if search_pattern in line:
                return i, file_path

    return None, None
