import json
import os

from actions_logging.app_logging import logger
from github.env import (exit_on_error_and_write_summary, get_required_env_var,
                        write_github_env, write_github_output)
from terragrunt.drift_detection import get_plan_changes, compare_plans_for_drift
from terragrunt.tg_common import (download_file_from_s3, get_bucket_name,
                               get_modules_versions, get_s3_path,
                               sanitize_work_dir)
from terragrunt.constants import (CANDIDATE_SUFFIX,TF_PLAN_JSON, APPLY, DESTROY, PLAN, PLAN_DESTROY)
from terragrunt.iac_commands import run_iac_command, validate_tool
from terragrunt.slack import set_slack_status, format_changes_for_slack_message, prepend_message_with_inputs

def update_plan_summary(plan_summary, mode, resource_type, resource_name):
    resources = plan_summary.get(mode, {})
    if not resources.get(resource_type, None):
        resources[resource_type] = [resource_name]
    else:
        resources[resource_type].append(resource_name)
    plan_summary[mode] = resources

def build_plan_summary(work_dir, tool, drift):
    """
    Build a summary of the Terraform or Terragrunt plan and log the changes.

    This function generates a JSON representation of the plan, processes the changes,
    and formats a summary message for Slack.

    Args:
        work_dir (str): The working directory containing the plan.
        tool (str): The IaC tool to use ('terraform' or 'terragrunt').
        diff (str): The differences between the candidate plan and the current plan.

    Raises:
        SystemExit: If an unsupported resource change mode is encountered or if an error occurs
                    while reading the JSON plan.
    """
    run_iac_command(work_dir, tool, 'show-json')
    try:
        with open(os.path.join(work_dir, TF_PLAN_JSON), 'r') as file:
            plan = json.load(file)
            plan_summary = {}
            for change in plan.get('resource_changes', []):
                resource_type = change.get('type')
                resource_name = change.get('name')
                actions = change.get('change', {}).get('actions', [])
                # see https://developer.hashicorp.com/terraform/internals/json-format#change-representation
                if actions in [['no-op'], ['read']]:
                    continue
                elif actions == ['create', 'delete'] or actions == ['delete', 'create']:
                    update_plan_summary(plan_summary, 'delete-and-create', resource_type, resource_name)
                    continue
                elif actions[0] in ['create', 'update', 'delete'] and len(actions) == 1:
                    update_plan_summary(plan_summary, actions[0], resource_type, resource_name)
                    continue
                else:
                    exit_on_error_and_write_summary(f"Unsupported resource change mode in actions for change: {change}. supported actions are 'create', 'update', 'delete'")

            formatted_changes = format_changes_for_slack_message(plan_summary, drift)
            return formatted_changes

    except Exception as e:
        logger.error(f"couldn't read Terraform plan in json format due to error: {e}")


def main():
    """
    Main function to execute an IaC command in a sanitized working directory.

    This function performs the following steps:
    1. Sanitizes the work directory path.
    2. Retrieves the tool to use from environment variables.
    3. Validates the tool.
    4. Retrieves the command and arguments from environment variables.
    5. Runs the specified IaC command.

    Environment Variables:
        WORK_DIR (str): The working directory.
        TOOL (str): The tool to use ('terraform' or 'terragrunt').
        COMMAND (str): The command to run.
        ARGS (str): Additional arguments for the command.

    Raises:
        SystemExit: If any critical error occurs during the process.
    """
    try:
        tool = get_required_env_var('TOOL')
        work_dir = sanitize_work_dir(get_required_env_var('WORK_DIR'))
        env_name = get_required_env_var('ENV_NAME')
        plan_version = os.getenv('PLAN_VERSION')
        exec_mode = os.getenv('EXECUTION_MODE', APPLY)
        github_repository = get_required_env_var('GITHUB_REPOSITORY')

        validate_tool(tool)

        logger.warning("Displaying current plan")
        cmd = PLAN_DESTROY if exec_mode == DESTROY else PLAN
        run_iac_command(work_dir, tool, cmd)

        bucket_name = get_bucket_name(env_name)
        s3_plan_folder = get_s3_path(github_repository, work_dir, PLAN)

        drift = None
        slack_title = None
        logger.warning("getting json from current plan to generate plan summary")
        run_iac_command(work_dir, tool, 'show-and-save')
        # Destroy is destructive operation (surprise) so better check everything before
        if exec_mode == DESTROY:
            drift = compare_plans_for_drift(work_dir, tool, bucket_name, s3_plan_folder, exec_mode=DESTROY)
            slack_title = f"Destroy plan summary for environment {env_name}, folder {work_dir}"
        # if a previous semver is deployed there's no point in checking drift with a stale plan of that version
        # current infra is in different state anyway
        # else if this is a candidate plan, check for drift
        elif os.getenv('CHECKOUT_TAG', '').endswith(CANDIDATE_SUFFIX):
            drift = compare_plans_for_drift(work_dir, tool, bucket_name, s3_plan_folder, exec_mode, plan_version)
        # In case this is a re-run of a version there should be no changes unless changes were done externally to current state
        else:
            drift = get_plan_changes(work_dir)

        if exec_mode == APPLY:
            slack_title = f"Drift detected before applying plan"
            if not drift:
                slack_title = f"No drift detected before applying plan"
            modules_versions_semver_file = get_modules_versions(plan_version)
            modules_versions_s3_path = os.path.join(s3_plan_folder, modules_versions_semver_file)
            download_file_from_s3(bucket_name, modules_versions_s3_path, os.path.join(work_dir, modules_versions_semver_file))

        plan_summary = build_plan_summary(work_dir, tool, drift)
        formatted_plan_summary = prepend_message_with_inputs(plan_summary, work_dir, env_name, plan_version, exec_mode)
        write_github_env(formatted_plan_summary, 'PLAN_SUMMARY')
        write_github_output(formatted_plan_summary, 'PLAN_SUMMARY')
        write_github_env(slack_title, 'SLACK_TITLE')
        set_slack_status(exec_mode, drift)
    except Exception as e:
        exit_on_error_and_write_summary(f"Error occurred during plan diff and summarize: {e}")

if __name__ == "__main__":
    main()
