#!/usr/bin/env python3
##############################################################################
# MIT License
#
# Copyright (c) 2025 Advanced Micro Devices, Inc. All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

##############################################################################

"""
Analysis Config Differentiation Script
Generates differences from curr arch directory to prev arch directory.
Output shows what needs to change in prev arch to match curr arch.
"""

from __future__ import annotations

import sys
from pathlib import Path

try:
    from . import utils as cm_utils
except Exception:
    repo_root = Path(__file__).resolve().parents[1]
    if str(repo_root) not in sys.path:
        sys.path.insert(0, str(repo_root))
    try:
        import config_management.utils as cm_utils  # type: ignore
    except Exception:
        import utils as cm_utils  # type: ignore

AUTOGEN_TEXT = (
    "# AUTOGENERATED FILE. Only edit for testing purposes, not for development. "
    "Generated by tools/config_management/generate_config_deltas.py\n"
)


def get_metric_tables(data: dict) -> list[dict]:
    """Extract all metric tables from data source."""
    tables: list[dict] = []
    for item in data.get("Panel Config", {}).get("data source", []):
        mt = item.get("metric_table")
        if isinstance(mt, dict):
            tables.append(mt)
    return tables


def get_metric_descriptions(data: dict) -> dict:
    """Extract metric descriptions from panel config."""
    return data.get("Panel Config", {}).get("metrics_description", {}) or {}


def compare_metrics(
    prev_metrics: dict, curr_metrics: dict
) -> tuple[list[dict], list[dict], list[dict]]:
    """Compare metrics and return (additions, deletions, modifications)."""
    prev_keys = set(prev_metrics.keys())
    curr_keys = set(curr_metrics.keys())

    additions = [{name: curr_metrics[name]} for name in sorted(curr_keys - prev_keys)]
    deletions = [{name: prev_metrics[name]} for name in sorted(prev_keys - curr_keys)]

    modifications: list[dict] = []
    for name in sorted(prev_keys & curr_keys):
        if prev_metrics[name] != curr_metrics[name]:
            all_fields = set(prev_metrics[name].keys()) | set(curr_metrics[name].keys())
            modified_fields = {
                field: curr_metrics[name].get(field)
                for field in all_fields
                if prev_metrics[name].get(field) != curr_metrics[name].get(field)
            }
            if modified_fields:
                modifications.append({name: modified_fields})

    return additions, deletions, modifications


def compare_descriptions(
    prev_descriptions: dict, curr_descriptions: dict
) -> tuple[dict, dict, dict]:
    """
    Compare metric descriptions and return (additions, deletions, modifications).
    Values are dicts with 'plain' and 'rst'.
    """
    prev_keys = set(prev_descriptions.keys())
    curr_keys = set(curr_descriptions.keys())

    additions: dict = {}
    deletions: dict = {}
    modifications: dict = {}

    for name in sorted(curr_keys - prev_keys):
        desc = curr_descriptions[name]
        additions[name] = (
            desc if isinstance(desc, dict) else {"plain": desc, "rst": desc}
        )

    for name in sorted(prev_keys - curr_keys):
        desc = prev_descriptions[name]
        deletions[name] = (
            desc if isinstance(desc, dict) else {"plain": desc, "rst": desc}
        )

    for name in sorted(prev_keys & curr_keys):
        prev_desc = prev_descriptions[name]
        curr_desc = curr_descriptions[name]

        prev_plain = (
            prev_desc if isinstance(prev_desc, str) else prev_desc.get("plain", "")
        )
        curr_plain = (
            curr_desc if isinstance(curr_desc, str) else curr_desc.get("plain", "")
        )

        prev_rst = (
            prev_desc
            if isinstance(prev_desc, str)
            else prev_desc.get("rst", prev_plain)
        )
        curr_rst = (
            curr_desc
            if isinstance(curr_desc, str)
            else curr_desc.get("rst", curr_plain)
        )

        if prev_plain != curr_plain or prev_rst != curr_rst:
            modifications[name] = {"plain": curr_plain, "rst": curr_rst}

    return additions, deletions, modifications


def compare_tables(
    prev_tables: list[dict], curr_tables: list[dict]
) -> tuple[list[dict], list[dict], list[dict]]:
    """Compare tables and return (additions, deletions, modifications)."""
    prev_dict = {t["id"]: t for t in prev_tables}
    curr_dict = {t["id"]: t for t in curr_tables}

    prev_ids = set(prev_dict.keys())
    curr_ids = set(curr_dict.keys())

    additions: list[dict] = []
    deletions: list[dict] = []
    modifications: list[dict] = []

    additions.extend(curr_dict[tid] for tid in sorted(curr_ids - prev_ids))
    deletions.extend(prev_dict[tid] for tid in sorted(prev_ids - curr_ids))

    for tid in sorted(prev_ids & curr_ids):
        prev_metrics = prev_dict[tid].get("metric", {}) or {}
        curr_metrics = curr_dict[tid].get("metric", {}) or {}

        metric_adds, metric_dels, metric_mods = compare_metrics(
            prev_metrics, curr_metrics
        )

        if metric_adds:
            additions.append({
                "id": tid,
                "title": curr_dict[tid].get("title"),
                "metrics": metric_adds,
            })
        if metric_dels:
            deletions.append({
                "id": tid,
                "title": prev_dict[tid].get("title"),
                "metrics": metric_dels,
            })
        if metric_mods:
            modifications.append({
                "id": tid,
                "title": curr_dict[tid].get("title"),
                "metrics": metric_mods,
            })

    return additions, deletions, modifications


def format_metric_fields(metric_data: dict) -> list[str]:
    """Format metric fields as YAML lines."""
    lines: list[str] = []
    for field_name, field_value in metric_data.items():
        if isinstance(field_value, str) and (
            "\n" in field_value or len(field_value) > 80
        ):
            lines.append(f"                {field_name}: |")
            lines.extend(
                f"                  {line}" for line in field_value.split("\n")
            )
        else:
            lines.append(f"                {field_name}: {field_value}")
    return lines


def format_description_fields(desc_data: dict) -> list[str]:
    """Format description fields as YAML lines."""
    lines: list[str] = []
    for field_name, field_value in desc_data.items():
        if isinstance(field_value, str) and (
            "\n" in field_value or len(field_value) > 80
        ):
            lines.append(f"          {field_name}: |")
            lines.extend(f"            {line}" for line in field_value.split("\n"))
        else:
            lines.append(f"          {field_name}: {field_value}")
    return lines


def format_output(combined_diff: dict) -> str:
    """Format the diff dictionary into a YAML string."""
    lines: list[str] = []
    for category in ("Addition", "Deletion", "Modification"):
        lines.append(f"{category}:")
        if not combined_diff.get(category):
            lines.append("  []")
            lines.append("")
            continue

        for panel_item in combined_diff[category]:
            pc = panel_item["panel_config"]
            lines.extend([
                "  - Panel Config:",
                f"      id: {pc['id']}",
                f"      title: {pc['title']}",
            ])

            if panel_item.get("metric_tables"):
                lines.append("    metric_tables:")
                for mt in panel_item["metric_tables"]:
                    lines.extend([
                        "      - metric_table:",
                        f"          id: {mt['id']}",
                        f"          title: {mt['title']}",
                        "          metrics:",
                    ])
                    metrics_to_format = mt.get("metrics") or [
                        {name: data} for name, data in (mt.get("metric") or {}).items()
                    ]
                    for metric in metrics_to_format:
                        for metric_name, metric_data in metric.items():
                            lines.append(f"            - {metric_name}:")
                            lines.extend(format_metric_fields(metric_data))

            if panel_item.get("metric_descriptions"):
                lines.append("    metric_descriptions:")
                for metric_name, desc_data in panel_item["metric_descriptions"].items():
                    lines.append(f"      {metric_name}:")
                    lines.extend(format_description_fields(desc_data))

        lines.append("")
    return "\n".join(lines)


def main() -> None:
    if len(sys.argv) != 3:
        print("Usage: python generate_config_deltas.py <curr_arch_dir> <prev_arch_dir>")
        sys.exit(1)

    curr_arch_dir = Path(sys.argv[1])
    prev_arch_dir = Path(sys.argv[2])

    if not curr_arch_dir.is_dir() or not prev_arch_dir.is_dir():
        print("Error: Both arguments must be directories")
        sys.exit(1)

    curr_files = {f.name for f in curr_arch_dir.glob("*.yaml")}
    prev_files = {f.name for f in prev_arch_dir.glob("*.yaml")}
    common_files = curr_files & prev_files

    if not common_files:
        print("Error: No common YAML files found")
        sys.exit(1)

    print(f"Comparing {len(common_files)} files...")

    combined_diff = {"Addition": [], "Deletion": [], "Modification": []}

    for filename in sorted(common_files):
        curr_data = cm_utils.load_yaml(curr_arch_dir / filename)
        prev_data = cm_utils.load_yaml(prev_arch_dir / filename)

        curr_pc = curr_data.get("Panel Config", {}) or {}
        prev_pc = prev_data.get("Panel Config", {}) or {}

        curr_tables = get_metric_tables(curr_data)
        prev_tables = get_metric_tables(prev_data)

        curr_descriptions = get_metric_descriptions(curr_data)
        prev_descriptions = get_metric_descriptions(prev_data)

        table_adds, table_dels, table_mods = compare_tables(prev_tables, curr_tables)
        desc_adds, desc_dels, desc_mods = compare_descriptions(
            prev_descriptions, curr_descriptions
        )

        if table_adds or desc_adds:
            entry = {
                "panel_config": {"id": curr_pc.get("id"), "title": curr_pc.get("title")}
            }
            if table_adds:
                entry["metric_tables"] = table_adds
            if desc_adds:
                entry["metric_descriptions"] = desc_adds
            combined_diff["Addition"].append(entry)

        if table_dels or desc_dels:
            entry = {
                "panel_config": {"id": prev_pc.get("id"), "title": prev_pc.get("title")}
            }
            if table_dels:
                entry["metric_tables"] = table_dels
            if desc_dels:
                entry["metric_descriptions"] = desc_dels
            combined_diff["Deletion"].append(entry)

        if table_mods or desc_mods:
            entry = {
                "panel_config": {"id": curr_pc.get("id"), "title": curr_pc.get("title")}
            }
            if table_mods:
                entry["metric_tables"] = table_mods
            if desc_mods:
                entry["metric_descriptions"] = desc_mods
            combined_diff["Modification"].append(entry)

    output = AUTOGEN_TEXT + format_output(combined_diff)

    print("\n" + "=" * 80)
    print("COMBINED DIFF OUTPUT:")
    print("=" * 80)
    print(output)

    output_dir = prev_arch_dir / "config_delta"
    output_dir.mkdir(exist_ok=True)
    output_file = output_dir / f"{curr_arch_dir.name}_diff.yaml"
    with open(output_file, "w") as f:
        f.write(output)

    print(f"\nDiff written to: {output_file}")


if __name__ == "__main__":
    main()
