#!/usr/bin/env python3

import logging
import os
import platform
import psutil
import sys
import re
import subprocess
from difflib import ndiff
from pathlib import Path

try:
    from kortical import api
    from kortical import config
    from kortical.api.project import Project
    from kortical.api.project import Environment
    from kortical.cli.helpers.component_helpers import display_list_component_instances
    from kortical.helpers.exceptions import KorticalKnownException
    from kortical.helpers.print_helpers import print_info, print_title, print_question, print_success, display_list, format_question, print_warning, print_error
except Exception as e:
    print("Ensure that you have enabled your venv that includes the kortical package, before running git commit, if you want kortical pre-commit checks to run.")
    exit(0)


def is_interactive_mode():
    """
    Check if the script is running in an environment that allows
    interactive input (i.e., sys.stdin is attached to a TTY).
    """
    current_process = psutil.Process(os.getpid())
    parent = current_process.parent()
    if parent is not None:
        grandparent = parent.parent()
        if grandparent is not None and grandparent.name() != 'pycharm':
            return True
    return sys.stdin.isatty()


def find_git_root_directory():
    git_folder = '.git'
    current_directory = os.getcwd()
    while True:
        path = os.path.join(current_directory, git_folder)
        if os.path.isdir(path):
            return current_directory
        path = Path(current_directory)
        current_directory = str(path.parent.absolute())
        if path.root == current_directory:
            print_error(f'Could not find [{git_folder}] in any parent directory.')
            exit(1)


def initialise_api_and_get_key_variables():
    try:
        api.init()
        system_url = config.get('system_url')
        project = Project.get_selected_project(throw=False)
        environment = Environment.get_selected_environment(project)
    except KorticalKnownException as e:
        print_error(str(e))
        print_question(
            f"Given this error kortical pre-commit checks will not run, do you still want to proceed with this commit? [y/n]")
        response = input()
        if not response or response.lower()[0] != 'y':
            exit(1)
        else:
            exit(0)

    if system_url is None:
        print_warning(f"""Please ensure that you've got an active kortical config that's pointing to a kortical system.

    You can create intialize kortical config with the following command [kortical config init].""")
        print_question(f"Are you sure you want to continue without this setup? [y/n]")
        response = input()
        if not response or response.lower()[0] != 'y':
            exit(1)
        else:
            exit(0)
    if project is None:
        print_warning(f"""Please ensure that you've selected a project before committing to this repo. In order for the continuous integration to work, it needs a project to run in.

        You can create a project using the command [kortical project create <project_name>].

        You can select an existing project using the command [kortical project select <project_name>]""")
        print_question(f"Are you sure you want to continue without this setup? [y/n]")
        response = input()
        if not response or response.lower()[0] != 'y':
            exit(1)
        else:
            exit(0)

    print_title(f"system_url [{system_url}], project [{project}], environment [{environment}]")

    first_environment = project.list_environments()[0]

    return system_url, project, environment, first_environment


def rationalise_github_workflows(root_folder, system_url, project, environment, first_environment):
    variables = {
        r'(kortical config set system_url )(.*)(\n)': system_url,
        r'(kortical project select )(.*)(\n)': f'"{project.name}"',
        r'(--from=)(.*)( --component-config="config/component_config\.yml")': first_environment.id
    }
    workflow_files = [
        ".github/workflows/continuous_integration.yml",
        ".github/workflows/on_merge_master.yml",
        ".github/workflows/promote_to_uat.yml",
        ".github/workflows/promote_to_production.yml"
    ]
    show_message = True
    for workflow_file in workflow_files:
        workflow_file_full_path = os.path.join(root_folder, workflow_file)
        with open(workflow_file_full_path, 'r') as f:
            workflow_file_text_original = f.read()
        workflow_file_text = workflow_file_text_original

        first_replace = "<system_url>" in workflow_file_text

        for regex, value in variables.items():
            workflow_file_text = re.sub(regex, f"\\g<1>{value}\\g<3>", workflow_file_text)

        if workflow_file_text_original != workflow_file_text:
            if show_message:
                print_info("Updating github workflows to point to the selected system and project")
                show_message = False
            with open(workflow_file_full_path, 'w') as f:
                f.write(workflow_file_text)
            subprocess.run(['git', 'add', workflow_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)


def rationalise_component_config(root_folder, system_url, project, environment, first_environment):
    """
    Checks whether there's a difference between local component_config.yml and the
    environment's component config. Also checks if component_config.yml is missing.

    Returns True if the user needs to intervene (missing file or differences), or False if no changes.
    This function does NOT ask for user input and does NOT overwrite files -- that is handled in main.
    """
    if not environment.is_challenger():
        print_warning(f"Environment [{environment}] is not a challenger environment!!")

    component_config_cloud = environment.get_component_config()
    component_config_local_file_path = os.path.join('config', 'component_config.yml')
    component_config_local_file_path_full = os.path.join(root_folder, component_config_local_file_path)

    # Check if local file exists
    if not os.path.isfile(component_config_local_file_path_full):
        # File missing => definitely user intervention needed
        display_list_component_instances(project, environment)
        print_title(f"Component config for environment [{environment.name}]")
        print_info(component_config_cloud)
        print_title("--------")
        print_info(f"""We did not find a component_config file at [{component_config_local_file_path}].

This likely means it's your first time doing a commit for this app. The component config stores
all of the components and versions that are expected to run with this commit of the code. Once this
commit is merged to master, all of the component versions listed here will be promoted to environment
[{first_environment}], so it's important that we're saving the config from the correct environment.""")

        return True

    # If file DOES exist, check for differences
    with open(component_config_local_file_path_full, 'r') as f:
        component_config_local = f.read()

    diff = ndiff(component_config_local.splitlines(keepends=True), component_config_cloud.splitlines(keepends=True))
    diff_list = list(diff)
    has_diff = not all([x[0] == ' ' for x in diff_list])

    if has_diff:
        display_list_component_instances(project, environment)
        print_title(
            f"Diff for component config in environment [{environment.name}] and [{component_config_local_file_path}]")
        print_info(''.join(diff_list), end="")
        print_title("--------")
        print_info(f"It looks like you've modified some components in your environment [{environment.name}] "
                   f"since your last commit (or vice versa).")

        return True
    else:
        print_info("No component_config changes detected")
        return False


if __name__ == '__main__':
    # Only re-assign sys.stdin if we're in interactive mode
    if is_interactive_mode():
        # ensure that we can get user input
        if platform.system() == 'Windows':
            sys.stdin = open('CON:', mode='r', encoding=sys.stdin.encoding)
        else:
            sys.stdin = open("/dev/tty", "r")

    # stop spam logging from DEBUG
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger()
    logger.setLevel(level=logging.INFO)

    root_folder = find_git_root_directory()
    system_url, project, environment, first_environment = initialise_api_and_get_key_variables()
    rationalise_github_workflows(root_folder, system_url, project, environment, first_environment)

    # Now see if local config differs from environment config
    needs_intervention = rationalise_component_config(root_folder, system_url, project, environment, first_environment)
    if needs_intervention:
        if is_interactive_mode():
            print_question(
                "Would you like to overwrite/create your local component_config.yml "
                "with the one from the environment? [y/n] (If in doubt, yes is probably right.)"
            )
            response = input()
            if not response or response.lower()[0] != 'y':
                print_warning(
                    "Skipping overwrite. If this mismatch is intentional, continue. Otherwise fix before commit.")
                # If you want to abort, do: sys.exit(1)
            else:
                # Overwrite or create the file with cloud config
                config_local_path = os.path.join(root_folder, 'config', 'component_config.yml')
                component_config_cloud = environment.get_component_config()
                with open(config_local_path, 'w') as f:
                    f.write(component_config_cloud)
                print_success(f"Component config file saved to [{config_local_path}].")
                subprocess.run(['git', 'add', config_local_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        else:
            # Non-interactive mode: raise an error so the user sees it in PyCharm / CI logs
            raise RuntimeError(
                "There is a difference between the local component_config and the environment, or the file is missing. "
                "Please run `python .git_hooks/pre-commit` locally in interactive mode to resolve."
            )
