import fileinput
import os
import re
import shutil

import hcl2
from actions_logging.app_logging import logger
from common.common import raise_with_context
from git.utils import create_branch, git_add_commit_and_push
from github.env import exit_on_error_and_write_summary, get_required_env_var, write_github_summary
from github.github_apis import branch_exist, create_pull_request, get_default_branch
from terragrunt.constants import NETWORK_SUBNET_INPUT_KEYS


def get_sw_env_dir_name(env_name: str) -> str:
    """
    Returns the name of the directory storing the starwars environment.

    :param env_name: The name of the environment.
    :return: The starwars environment directory name.
    """
    return f"qa-{env_name}"


def get_existing_subnets(source_path: str) -> list:
    """
    Extracts existing subnet CIDRs from the source network file.

    :param source_path: The path to the source environment directory.
    :return: A list of four /24 subnet CIDRs.
    """
    network_file = os.path.join(source_path, "infra/us-east-1/network/terragrunt.hcl")
    subnets = []

    try:
        with open(network_file, "r") as f:
            data = hcl2.load(f)
            if not data:
                raise_with_context(None, ValueError, f"Failed to load HCL data from {network_file}")
            inputs = data.get("inputs")
            if inputs is None:
                raise_with_context(None, ValueError, f"No inputs found in {network_file}")

            for key in NETWORK_SUBNET_INPUT_KEYS:
                subnets.append(inputs.get(key))
    except FileNotFoundError as e:
        raise_with_context(e, FileNotFoundError, f"Network file not found: {network_file}")
    except hcl2.Hcl2Error as e:
        raise_with_context(e, ValueError, f"Error parsing HCL file {network_file}")
    except Exception as e:
        raise_with_context(e, RuntimeError, f"Unexpected error while processing file {network_file}")

    return subnets


def calculate_new_slash_24_subnets(network_cidr: str) -> list:
    """
    Calculates a /24 subnet from a given /22 CIDR block for each subnet.

    :param network_cidr: The CIDR block to calculate subnets from.
    :return: A list of four /24 subnet CIDRs.
    """
    base_ip = network_cidr.split("/")[0]
    octets = base_ip.split(".")
    returned_subnet_cidr_list = []
    next_octet_1 = int(octets[0])
    next_octet_2 = int(octets[1])
    next_octet_3 = int(octets[2])
    for i in range(len(NETWORK_SUBNET_INPUT_KEYS)):
        cidr = f"{next_octet_1}.{next_octet_2}.{next_octet_3}.0/24"
        returned_subnet_cidr_list.append(cidr)
        logger.debug(f"Calculated subnet #{i}: {cidr}")
        next_octet_3 += 1
        if next_octet_3 > 255:
            next_octet_3 = 0
            next_octet_2 += 1
    return returned_subnet_cidr_list


def duplicate_environment(ga_repo_path: str, env_name_to_copy_from: str, env_name: str, network_cidr: str):
    """ "
    Duplicates an environment by copying the source environment directory and replacing occurrences of the source
    environment name and CIDRs.

    :param ga_repo_path: The path to the GA repository.
    :param env_name_to_copy_from: The name of the environment to copy from.
    :param env_name: The name of the new environment to create.
    :param network_cidr: The /22 CIDR block to calculate new subnets from.
    """

    source_path = os.path.join(ga_repo_path, get_sw_env_dir_name(env_name_to_copy_from))
    target_path = os.path.join(ga_repo_path, get_sw_env_dir_name(env_name))

    logger.info(f"Starting duplication from {source_path} to {target_path}...")

    if not os.path.exists(source_path):
        raise_with_context(None, FileNotFoundError, f"Source environment {env_name_to_copy_from} does not exist.")

    if os.path.exists(target_path):
        raise_with_context(None, FileExistsError, f"Target environment {env_name} already exists.")

    shutil.copytree(source_path, target_path)

    logger.debug("Extracting existing subnets...")
    copied_env_subnets = get_existing_subnets(source_path)
    new_env_subnets = calculate_new_slash_24_subnets(network_cidr)
    logger.debug(f"Subnets in copied environment {copied_env_subnets} to be replaced with {new_env_subnets}")

    logger.debug("Replacing occurrences of environment name and CIDR in files...")
    cidr_pattern = r"172\.\d+\.\d+\.\d+/22"  # Regex to match any 172.x.x.x/22 CIDR

    logger.debug("Replacing occurrences of environment name and CIDRs in files...")
    for root, _, files in os.walk(target_path):
        for file in files:
            file_path = os.path.join(root, file)
            logger.debug(f"Processing file: {file_path}")
            try:
                with fileinput.FileInput(file_path, inplace=True) as file:
                    for line in file:
                        modified_line = line.replace(env_name_to_copy_from, env_name)
                        modified_line = re.sub(cidr_pattern, network_cidr, modified_line)
                        for old, new in zip(copied_env_subnets, new_env_subnets):
                            modified_line = modified_line.replace(old, new)
                        # Print the modified line back to the file
                        print(modified_line, end="")
            except Exception as e:
                raise_with_context(e, RuntimeError, f"Error processing file {file_path}")

    logger.info(f"Environment {env_name_to_copy_from} duplicated as {env_name} successfully.")


def main():
    jira_ticket = get_required_env_var("JIRA_TICKET")
    ga_git_repo = get_required_env_var("GA_GIT_REPO")
    ga_repo_path = os.path.abspath(get_required_env_var("GA_REPO_PATH"))
    env_name_to_copy_from = get_required_env_var("ENV_NAME_TO_COPY_FROM")
    env_name = get_required_env_var("ENV_NAME")
    network_cidr = get_required_env_var("NETWORK_CIDR")
    branch_name = f"{jira_ticket}-creating-starwars-env-{env_name}"

    try:
        default_branch = get_default_branch(ga_git_repo)

        if os.path.exists(os.path.join(ga_repo_path, get_sw_env_dir_name(env_name))):
            write_github_summary(
                (
                    f"{ga_git_repo} already has directory {os.path.join(ga_repo_path, get_sw_env_dir_name(env_name))} "
                    f"in branch {default_branch}."
                )
            )
            return

        if branch_exist(ga_git_repo, branch_name):
            write_github_summary(f"{ga_git_repo} already has branch {branch_name}")
            return

        create_branch(ga_repo_path, branch_name)
        duplicate_environment(ga_repo_path, env_name_to_copy_from, env_name, network_cidr)
        git_add_commit_and_push(
            get_sw_env_dir_name(env_name),
            f"{jira_ticket} Duplicating environment {env_name_to_copy_from} to {env_name} with CIDR {network_cidr}",
            branch_name,
        )

        pr_title = f"{jira_ticket} Creating new SW environment {env_name}"
        pr_description = (
            f"This PR was created programatically via GitHub action to create a new starwars environment {env_name} "
            f"(copied from {env_name_to_copy_from} with VPC CIDR {network_cidr}"
        )
        pr_number, pr_url = create_pull_request(ga_git_repo, branch_name, default_branch, pr_title, pr_description)
        if pr_number:
            write_github_summary(f"PR created: {pr_url}")
        else:
            exit_on_error_and_write_summary(f"Error creating PR for repository {ga_git_repo}")
    except Exception as e:
        exit_on_error_and_write_summary(f"Error in duplicating environment: {e}")


if __name__ == "__main__":
    main()
