"""Script to read conformance output and write to CSV.

Takes the conformance output that is generated by package_hub_fusion_compatibility
and converts it into CSV output for analysis.
Update OUTPUT_PATH, CONFORMANCE_OUTPUT_FILE_NAME, and FUSION_VERSION
Two CSVs are written to OUTPUT_PATH:
    "package_conformance_summary_v{fusion_version}_{run_date}.csv"
    "package_conformance_all_errors_v{fusion_version}_{run_date}.csv"
package_conformance_summary: one line per package version
package_conformance_all_errors: one line per error per package version
"""

import csv
import json
from collections import Counter, defaultdict
from datetime import datetime
from pathlib import Path
from typing import Any

OUTPUT_PATH: Path = Path.cwd() / "src" / "dbt_fusion_package_tools" / "scripts" / "output"
CONFORMANCE_OUTPUT_FILE_NAME: str = "v101_conformance_output_20260128.json"
FUSION_VERSION = "101"


def reload_output_from_file(
    file_path: Path,
) -> defaultdict[str, list[dict[str, Any]]]:
    with file_path.open("r", encoding="utf-8") as fh:
        return json.load(fh)


def group_errors(error_code: str, error_message: str) -> str:
    if error_code == "102":
        return "dbt102: Deprecated test argument/config at top level"
    elif error_code == "1005" and error_message in (
        "'data-paths' cannot be specified in dbt_project.yml",
        "'source-paths' cannot be specified in dbt_project.yml",
    ):
        return "dbt1005: data-paths or source-paths in dbt_project.yml"
    elif error_code == "1005" and len(error_message) > 21 and error_message[0:21] == "Found duplicate model":
        return "dbt1005: Found duplicate model"
    else:
        remove_file_name = error_message.split("-->")
        split_on_backtick = (remove_file_name[0]).split("`")
        split_on_period = (split_on_backtick[0]).split(".")
        return f"dbt{error_code}: {split_on_period[0]}"


def main():
    output_path: Path = OUTPUT_PATH
    conformance_output_file_name: str = CONFORMANCE_OUTPUT_FILE_NAME
    fusion_version = FUSION_VERSION
    run_date: str = datetime.today().strftime("%Y%m%d")
    package_summary_file_name: str = f"package_conformance_summary_v{fusion_version}_{run_date}.csv"
    package_errors_file_name: str = f"package_conformance_all_errors_v{fusion_version}_{run_date}.csv"
    conformance_output = reload_output_from_file(output_path / conformance_output_file_name)
    package_summary = []
    unique_error_codes: Counter[str] = Counter()
    unique_warning_codes: Counter[str] = Counter()
    all_errors = []
    for package_name in conformance_output:
        package: dict[str, Any] = conformance_output[package_name]
        for version in package:
            package_version: dict[str, Any] = package[version]
            parse_errors: set[str] = set()
            parse_warnings: set[str] = set()
            parse_short_errors: set[str] = set()
            parse_compatibility_result = package_version.get("parse_compatibility_result") or {}
            if parse_compatibility_result.get("errors"):
                for error in parse_compatibility_result["errors"]:
                    parse_errors.add(str(error["error_code"]))
                    unique_error_codes[str(error["error_code"])] += 1
                    short_error = group_errors(str(error["error_code"]), error["body"])
                    parse_short_errors.add(short_error)
                    all_errors.append(
                        {
                            "package_name": package_name,
                            "package_version": version,
                            "manually_verified_compatible": package[version]["manually_verified_compatible"],
                            "manually_verified_incompatible": package[version]["manually_verified_incompatible"],
                            "parse_compatible": package[version]["parse_compatible"],
                            "parse_error_code": str(error["error_code"]),
                            "parse_error": error["body"] if len(error["body"]) < 200 else error["body"][:200],
                            "parse_exit_code": parse_compatibility_result.get("parse_exit_code"),
                            "parse_total_errors": parse_compatibility_result.get("total_errors"),
                            "parse_total_warnings": parse_compatibility_result.get("total_warnings"),
                            "require_dbt_version_compatible": package[version]["require_dbt_version_compatible"],
                            "require_dbt_version_defined": package[version]["require_dbt_version_defined"],
                            "fusion_version": f"v{fusion_version}",
                            "parse_error_grouped": short_error,
                        }
                    )
            if parse_compatibility_result.get("warnings"):
                for warning in parse_compatibility_result["warnings"]:
                    parse_warnings.add(str(warning["error_code"]))
                    unique_warning_codes[str(error["error_code"])] += 1
            sorted_parse_short_errors: list[str] = sorted([x for x in parse_short_errors])
            package_summary.append(
                {
                    "package_name": package_name,
                    "package_version": version,
                    "manually_verified_compatible": package[version]["manually_verified_compatible"],
                    "manually_verified_incompatible": package[version]["manually_verified_incompatible"],
                    "parse_errors": ",".join([f"dbt{x}" for x in sorted(parse_errors)])
                    if len(parse_errors) > 0
                    else "",
                    "parse_warnings": ",".join(sorted(parse_warnings)) if len(parse_warnings) > 0 else "",
                    "parse_exit_code": parse_compatibility_result.get("parse_exit_code"),
                    "parse_total_errors": parse_compatibility_result.get("total_errors"),
                    "parse_total_warnings": parse_compatibility_result.get("total_warnings"),
                    "parse_compatible": package[version]["parse_compatible"],
                    "require_dbt_version_compatible": package[version]["require_dbt_version_compatible"],
                    "require_dbt_version_defined": package[version]["require_dbt_version_defined"],
                    "fusion_version": f"v{fusion_version}",
                    "short_error_total": len(parse_short_errors),
                    "short_error_1": sorted_parse_short_errors[0] if len(sorted_parse_short_errors) > 0 else "",
                    "short_error_2": sorted_parse_short_errors[1] if len(sorted_parse_short_errors) > 1 else "",
                    "download_failed": package[version]["download_failed"],
                }
            )
    print(f"unique error codes: {unique_error_codes}")
    print(f"unique warning codes: {unique_warning_codes}")

    with open(output_path / package_summary_file_name, mode="w") as file:
        writer = csv.DictWriter(file, fieldnames=[field for field in package_summary[0]])
        writer.writeheader()
        writer.writerows(package_summary)
    print(f"Wrote {len(package_summary)} rows to {output_path / package_summary_file_name}")

    with open(output_path / package_errors_file_name, mode="w") as file:
        writer = csv.DictWriter(file, fieldnames=[field for field in all_errors[0]])
        writer.writeheader()
        writer.writerows(all_errors)
    print(f"Wrote {len(all_errors)} rows to {output_path / package_errors_file_name}")


if __name__ == "__main__":
    main()
