import os
import subprocess
from app.utils.logging_utils import logger
import time
import re

HUNK_HEADER_REGEX = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')

def us_git_to_apply_code_diff(git_diff: str):
    """
    Apply a git diff to the user's codebase.

    This function takes a git diff as a string and applies it to the user's codebase
    specified by the ZIYA_USER_CODEBASE_DIR environment variable. It creates a temporary
    file with the diff content and uses the 'git apply' command to apply the changes.

    Args:
        git_diff (str): A string containing the git diff to be applied.

    Note:
        This function assumes that the git command-line tool is available in the system path.
        It uses the --ignore-whitespace and --ignore-space-change options with 'git apply'
        to handle potential whitespace issues.
    """

    user_codebase_dir = os.environ.get("ZIYA_USER_CODEBASE_DIR")
    if not user_codebase_dir:
        raise ValueError("ZIYA_USER_CODEBASE_DIR environment variable is not set")

    # Create a temporary file with the diff content and a timestamp
    timestamp = int(time.time() * 1000)  # Get current timestamp in milliseconds
    temp_file = os.path.join(user_codebase_dir, f'temp_{timestamp}.diff')

    with open(temp_file, 'w', newline='\n') as f:
        f.write(git_diff)
        # need to add a newline at the end of the file of git apply will fail
        f.write("\n")
    logger.info(f"Created temporary diff file: {temp_file}")

    try:
        # Apply the changes using git apply
        cmd = ['git', 'apply', '--verbose', '--ignore-whitespace', '--ignore-space-change', temp_file]
        logger.info(f"Executing command: {' '.join(cmd)}")
        result = subprocess.run(cmd, capture_output=True, text=True, cwd=user_codebase_dir, check=True)

        logger.info(f"Git apply stdout: {result.stdout}")
        logger.error(f"Git apply stderr: {result.stderr}")
        logger.info(f"Git apply return code: {result.returncode}")

    except Exception as e:
        logger.error(f"Error applying changes: {str(e)}", exc_info=True)
        raise
    finally:
        os.remove(temp_file)

def correct_git_diff(git_diff: str) -> str:
    """
    Corrects the hunk headers in a git diff string by recalculating the line counts and
    adjusting the starting line numbers in the new file, considering the cumulative effect of
    previous hunks.

    The function assumes that the `start_line_old` values in the hunk headers are correct,
    but the line counts and `start_line_new` may be incorrect due to manual edits or errors.

    Parameters:
        git_diff (str): The git diff string to be corrected. It may contain multiple hunks
                        and incorrect hunk headers.

    Returns:
        str: The git diff string with corrected hunk headers.

    The function works by parsing the diff, recalculating the line counts for each hunk,
    adjusting the `start_line_new` values based on the cumulative changes from previous hunks,
    and reconstructing the diff with corrected hunk headers.

    Notes:
        - The diff is expected to be in unified diff format.
        - Lines starting with '+++', '---', or other file headers are not treated as additions or deletions.
        - Context lines are lines that do not start with '+', '-', or '@@'.
        - All lines in the hunk (including empty lines) are considered in the counts.

    """
    # Split the diff into lines
    lines = git_diff.split('\n')

    corrected_lines = []
    line_index = 0

    # Keep track of the cumulative line number adjustments
    cumulative_line_offset = 0


    while line_index < len(lines):
        line = lines[line_index]
        hunk_match = HUNK_HEADER_REGEX.match(line)

        if hunk_match:
            # Process the hunk
            corrected_hunk_header, hunk_lines, line_index, line_offset = _process_hunk(
                lines, line_index, cumulative_line_offset
            )

            # Update cumulative_line_offset
            cumulative_line_offset += line_offset

            # Append corrected hunk header and hunk lines
            corrected_lines.append(corrected_hunk_header)
            corrected_lines.extend(hunk_lines)
        else:
            # For non-hunk header lines, just append them
            corrected_lines.append(line)
            line_index += 1

    # Join the corrected lines back into a string
    corrected_diff = '\n'.join(corrected_lines)
    return corrected_diff

def _process_hunk(lines: list, start_index: int, cumulative_line_offset: int):
    """
    Processes a single hunk starting at start_index in lines, recalculates the line counts,
    and returns the corrected hunk header, hunk lines, and the updated index after the hunk.

    Parameters:
        lines (list): The list of lines from the diff.
        start_index (int): The index in lines where the hunk header is located.
        cumulative_line_offset (int): The cumulative line offset from previous hunks.

    Returns:
        tuple:
            - corrected_hunk_header (str): The corrected hunk header.
            - hunk_lines (list): The list of lines in the hunk (excluding the hunk header).
            - end_index (int): The index in lines after the hunk.
            - line_offset (int): The line offset caused by this hunk (to adjust future hunks).

    The function reads the hunk lines, counts the number of lines in the original and new files,
    and adjusts the starting line number in the new file based on cumulative changes.
    """

    line_index = start_index
    hunk_header_line = lines[line_index]
    hunk_match = HUNK_HEADER_REGEX.match(hunk_header_line)

    # Extract the starting line numbers from the hunk header
    start_line_old = int(hunk_match.group(1))

    # Initialize counts for recalculation
    actual_count_old = 0
    actual_count_new = 0

    # Move to the next line after the hunk header
    line_index += 1

    hunk_lines = []

    # Collect hunk lines until the next hunk header or end of diff
    while line_index < len(lines):
        hunk_line = lines[line_index]
        if HUNK_HEADER_REGEX.match(hunk_line):
            break
        else:
            hunk_lines.append(hunk_line)
            line_index += 1

    # Now process hunk_lines to calculate counts
    for hunk_line in hunk_lines:
        if hunk_line.startswith('+') and not hunk_line.startswith('+++'):
            actual_count_new += 1
        elif hunk_line.startswith('-') and not hunk_line.startswith('---'):
            actual_count_old += 1
        else:
            # Context line (unchanged line)
            actual_count_old += 1
            actual_count_new += 1

    # Adjust start_line_new considering previous line offsets
    corrected_start_line_new = start_line_old + cumulative_line_offset

    # Calculate line offset for subsequent hunks
    line_offset = actual_count_new - actual_count_old

    # Reconstruct the corrected hunk header
    corrected_hunk_header = _format_hunk_header(
        start_line_old, actual_count_old, corrected_start_line_new, actual_count_new
    )

    return corrected_hunk_header, hunk_lines, line_index, line_offset

def _format_hunk_header(start_old: int, count_old: int, start_new: int, count_new: int) -> str:
    """
    Formats the hunk header according to git diff syntax, omitting counts when they are 1.

    Parameters:
        start_old (int): Starting line number in the original file.
        count_old (int): Number of lines in the hunk in the original file.
        start_new (int): Starting line number in the new file.
        count_new (int): Number of lines in the hunk in the new file.

    Returns:
        str: The formatted hunk header.

    The hunk header format is:
        @@ -start_old[,count_old] +start_new[,count_new] @@

    If count_old or count_new is 1, the count is omitted.
    """
    # Omit counts when they are equal to 1
    old_part = f'-{start_old}'
    if count_old != 1:
        old_part += f',{count_old}'
    new_part = f'+{start_new}'
    if count_new != 1:
        new_part += f',{count_new}'
    return f'@@ {old_part} {new_part} @@'



if __name__ == '__main__':
    # TODO: Create unit test and move these code to unit test
    diff = """\
diff --git a/file.txt b/file.txt
index e69de29..4b825dc 100644
--- a/file.txt
+++ b/file.txt
@@ -1,5 +1,5 @@
 Line one
+Line two
 Line three
 Line four
 Line five
@@ -10,2 +10,2 @@
 Line ten
-Line eleven
+Line eleven modified"""
    print(correct_git_diff(diff))
