#!.venv/bin/python3
import json
from pathlib import Path
from typing import Optional
import networkx as nx
import argparse
from pydantic import BaseModel

from plockalyser.analyses import (
    analyse_centrality,
    analyse_connectivity,
    get_summary_statistics,
    format_summary_statistics,
    format_gini,
)
from plockalyser.dot import export_to_dot


class Dependency(BaseModel):
    model_config = {"arbitrary_types_allowed": True}
    version: Optional[str] = None
    resolved: Optional[str] = None
    overridden: Optional[bool] = None
    dependencies: Optional[dict[str, "Dependency"]] = None


class NpmLsError(BaseModel):
    code: str
    summary: str
    detail: str


class NpmLsOutput(BaseModel):
    version: str
    name: str
    problems: list[str]
    dependencies: dict[str, Dependency]


def load_and_parse(package_lock_path: Path) -> tuple[nx.DiGraph, NpmLsOutput]:
    """Load the npm ls output file and build a dependency graph."""
    with open(package_lock_path, "r") as f:
        data = json.load(f)

    data = NpmLsOutput(**data)

    root_name = "root"  # data.name
    root_version = ""  # data.version
    # root_id = f"{root_name}@{root_version}"
    root_id = f"{root_name}"
    graph = nx.DiGraph()

    # Add the root node
    graph.add_node(root_id, name=root_name, version=root_version, type="root")

    # Process dependencies at the top level
    _process_dependencies_tree(graph, data.dependencies, root_id)
    return (graph, data)


def _process_dependencies_tree(
    graph: nx.DiGraph, dependencies: dict[str, Dependency], parent_id: str
):
    for pkg_name, pkg_info in dependencies.items():
        version = pkg_info.version or "unknown"
        pkg_id = f"{pkg_name}@{version}"

        # Add the node if it doesn't exist
        if not graph.has_node(pkg_id):
            graph.add_node(
                pkg_id,
                name=pkg_name,
                version=version,
                type="dependency",
            )

        # Add the edge from parent to this dependency (installed dependency)
        graph.add_edge(parent_id, pkg_id, type="installed")

        # Recursively process nested dependencies
        if pkg_info.dependencies:
            _process_dependencies_tree(graph, pkg_info.dependencies, pkg_id)


def output_tables(tables: list[str], output_file: Optional[Path]):
    if not output_file:
        print("\n\n".join(tables))
        return

    with open(output_file, "w") as f:
        f.write("\n\n".join(tables))
        print(f"Wrote tables to {output_file}")


def main():
    parser = argparse.ArgumentParser(
        description="Analyse a dependency network generated by `npm ls --all --json`"
    )
    parser.add_argument("npm_ls_file", help="Path to package-lock.json file")
    parser.add_argument(
        "--tables",
        help="Export network analyses as Markdown tables",
        action="store_true",
    )
    parser.add_argument(
        "--dot", help="Export network to DOT format", action="store_true"
    )
    parser.add_argument("--output", help="File to output to", default=None)

    args = parser.parse_args()

    graph, _ = load_and_parse(args.npm_ls_file)

    if args.output and sum([args.dot, args.tables]) > 1:
        print("When --output is given, --dot and --tables are mutually exclusive.")
        return 1

    if args.dot:
        export_to_dot(graph, args.output)

    # Run analyses
    if args.tables:
        tables_output: list[str] = []
        tables_output.append(format_summary_statistics(get_summary_statistics(graph)))
        tables_output.append(analyse_connectivity(graph))
        tables_output.append(analyse_centrality(graph))
        tables_output.append(format_gini(graph))
        output_tables(tables_output, args.output)


if __name__ == "__main__":
    main()
