"""The AaC Specifications plugin implementation module."""

# NOTE: It is safe to edit this file.
# This file is only initially generated by aac gen-plugin, and it won't be overwritten if the file already exists.

# There may be some unused imports depending on the definition of the plugin...but that's ok
import csv
from typing import List
from os import path, makedirs
from aac.execute.aac_execution_result import (
    ExecutionResult,
    ExecutionStatus,
    ExecutionMessage,
    MessageLevel,
)
from aac.in_out.parser._parse_source import parse
from aac.context.definition import Definition


plugin_name = "Specifications"


def before_spec_csv_check(architecture_file: str, output_directory: str, run_check) -> ExecutionResult:
    """
    Run the Check AaC command before the spec-csv command

    Args:
        architecture_file (str): The file to process for spec content.
        output_directory (str): The directory to write csv spec content.
        run_check: The run_check command from aac.plugins.check

    Returns:
        An ExecutionResult which contains indication of success or failure, and a list of command line messages
    """
    return run_check(architecture_file, False, False)


def spec_csv(architecture_file: str, output_directory: str) -> ExecutionResult:
    """
    Generate a csv requirements table from a specification definition.

    Args:
        architecture_file (str): The file to process for spec content.
        output_directory (str): The directory to write csv spec content.

    Returns:
        An ExecutionResult which contains indication of success or failure, and a list of command line messages
    """
    reqs = {}
    req_specs = {}
    parsed_file = parse(architecture_file)
    for spec in parsed_file:
        definition = spec
        if definition.get_root_key() == "req":
            reqs[definition.name] = definition
        if definition.get_root_key() == "req_spec":
            req_specs[definition.name] = definition

    rows = {}
    for spec in req_specs:
        ret_val = _create_rows(req_specs, reqs, spec)
        rows[spec] = ret_val
    # rows is a dictionary of lists(each file) of dictionaries(the rows in each file)
    (success, message) = _parent_child_check(rows, reqs)
    if success is False:
        return ExecutionResult(plugin_name, "Specifications", ExecutionStatus.GENERAL_FAILURE, message)

    field_names = ["Spec Name", "Section", "ID", "Requirement", "Parents", "Children"]

    # just in case, let's make sure the output directory exists
    if not path.lexists(output_directory):
        makedirs(output_directory)

    file_counter = 0
    for spec_name in req_specs:
        file_name = spec_name + ".csv"
        file_name = file_name.replace(" ", "_")
        output_path = path.join(output_directory, file_name)
        with open(output_path, "w") as output:
            writer = csv.DictWriter(output, fieldnames=field_names)
            writer.writeheader()
            writer.writerows(rows[spec_name])
            file_counter = file_counter + 1
    status = ExecutionStatus.SUCCESS
    messages: list[ExecutionMessage] = []
    messages.append(ExecutionMessage(f"{file_counter} CSV spec files written to {output_directory}", MessageLevel.INFO, None, None))

    return ExecutionResult(plugin_name, "Specifications", status, messages)


def _create_rows(req_specs: dict, reqs: dict, spec: str) -> List[dict]:
    """
    Creates Rows for a CSV file.

    Args:
        req_specs(dict): A dictionary containing the req_spec definitions
        reqs(dict): A dictionary containing the req definitions
        spec(str): A key for req_specs, pointing to the spec for which the current csv file is being generated

    Returns:
        A List containing rows as dictionaries, with the key being the column name.
    """
    ret_val = []
    if req_specs[spec].get_root_key() == "req_spec":  # make sure we're actually working with a spec here
        if "requirements" in req_specs[spec].content:
            for req in req_specs[spec].structure["req_spec"]["requirements"]:
                if req in reqs:
                    requirement = reqs[req]
                    ret_val.append(_gen_spec_line_from_req_dict(req_specs[spec].name, "", requirement))

        if "sections" in req_specs[spec].content:
            for section in req_specs[spec].structure["req_spec"]["sections"]:
                for req in req_specs[section].structure["req_spec"]["requirements"]:
                    if req in reqs:
                        requirement = reqs[req]
                        ret_val.append(_gen_spec_line_from_req_dict(req_specs[spec].name, section, requirement))
    return ret_val


def _gen_spec_line_from_req_dict(spec_name: str, section_name: str, req: Definition) -> dict:
    """
    Creates a line for a CSV file

    Args:
        spec_name(str): The name of the specification
        section_name(str): The name of the section
        req(Definition): The definition object of a requirement

    Returns:
        A dictionary of row entries with the key being the column name
    """
    line = {}
    line["Spec Name"] = spec_name
    line["Section"] = section_name
    line["ID"] = req.structure["req"]["id"]
    line["Requirement"] = req.structure["req"]["shall"]
    parent_ids = ""
    if "parents" in req.content:
        for parent_id in req.structure["req"]["parents"]:
            if len(parent_ids) == 0:
                parent_ids = f"{parent_id}"
            else:
                parent_ids = f"{parent_ids} {parent_id}"

    line["Parents"] = parent_ids
    child_ids = ""
    if "children" in req.content:
        for child_id in req.structure["req"]["children"]:
            if len(child_ids) == 0:
                child_ids = f"{child_id}"
            else:
                child_ids = f"{child_ids} {child_id}"
    line["Children"] = child_ids
    return line


def _parent_child_check(rows: dict, reqs: dict) -> (bool, List[ExecutionMessage]):
    """
    Checks to confirm parents and children of requirements actually exist

    Args:
        rows(dict): A dictionary containing all the rows generated so far
        reqs(dict): A dictionary containing all of the requirement definitions

    Returns:
        A boolean value signifying if the check was successful or not
        A List of ExecutionMessages to be returned by spec_csv in case of a failure.
    """
    messages = []
    success = True
    for file in rows:
        for row in rows[file]:
            if row["Parents"] != "":
                if row["Parents"] not in reqs:
                    req = row["Parents"]
                    messages.append(ExecutionMessage(f"Parent {req} not found in requirements list", MessageLevel.INFO, None, None))
                    success = False

            if row["Children"] != "":
                if row["Children"] not in reqs:
                    req = row["Children"]
                    messages.append(ExecutionMessage(f"Child {req} not found in requirements list", MessageLevel.INFO, None, None))
                    success = False
    return (success, messages)
