import difflib
import shutil
import os

from dataclasses import dataclass
from pathlib     import Path
from typing      import List, Dict, Optional, Tuple


@dataclass
class DiffHunk:
 header:    str
 lines:     List[str]
 old_start: int
 old_count: int
 new_start: int
 new_count: int

@dataclass  
class DiffFile:
 old_path: str
 new_path: str
 hunks:    List[DiffHunk]

def parse_hunk_header(line: str) -> Optional[tuple[int, int, int, int]]:
 """
 [C: tests/test_diff_viewer.py:test_parse_hunk_header]
 """
 if not line.startswith("@@"): return None
 
 parts = line.split()
 if len(parts) < 2: return None
 
 old_part = parts[1][1:]
 new_part = parts[2][1:]
 
 old_parts = old_part.split(",")
 new_parts = new_part.split(",")
 
 old_start = int(old_parts[0])
 old_count = int(old_parts[1]) if len(old_parts) > 1 else 1
 new_start = int(new_parts[0])
 new_count = int(new_parts[1]) if len(new_parts) > 1 else 1
 
 return (old_start, old_count, new_start, new_count)

def parse_diff(diff_text: str) -> List[DiffFile]:
 """
  [C: src/gui_2.py:App.request_patch_from_tier4, tests/test_diff_viewer.py:test_diff_line_classification, tests/test_diff_viewer.py:test_parse_diff_empty, tests/test_diff_viewer.py:test_parse_diff_none, tests/test_diff_viewer.py:test_parse_diff_with_context, tests/test_diff_viewer.py:test_parse_multiple_files, tests/test_diff_viewer.py:test_parse_simple_diff]
 """
 if not diff_text or not diff_text.strip():
  return []
 
 files:        List[DiffFile] = []
 current_file: Optional[DiffFile] = None
 current_hunk: Optional[DiffHunk] = None
 
 for line in diff_text.split("\n"):
  if line.startswith("--- "):
   if current_file:
    if current_hunk:
     current_file.hunks.append(current_hunk)
     current_hunk = None
    files.append(current_file)
   
   path = line[4:]
   if path.startswith("a/"):
    path = path[2:]
   current_file = DiffFile(old_path=path, new_path="", hunks=[])
   
  elif line.startswith("+++ ") and current_file:
   path = line[4:]
   if path.startswith("b/"):
    path = path[2:]
   current_file.new_path = path
   
  elif line.startswith("@@") and current_file:
   if current_hunk:
    current_file.hunks.append(current_hunk)
   
   hunk_info = parse_hunk_header(line)
   if hunk_info:
    old_start, old_count, new_start, new_count = hunk_info
    current_hunk = DiffHunk(
     header    = line,
     lines     = [],
     old_start = old_start,
     old_count = old_count,
     new_start = new_start,
     new_count = new_count
    )
   else:
    current_hunk = DiffHunk(
     header    = line,
     lines     = [],
     old_start = 0,
     old_count = 0,
     new_start = 0,
     new_count = 0
    )
    
  elif current_hunk is not None:
   current_hunk.lines.append(line)
   
  elif line and not line.startswith("diff ") and not line.startswith("index "):
   pass
 
 if current_file:
  if current_hunk:
   current_file.hunks.append(current_hunk)
  files.append(current_file)
 
 return files

def get_line_color(line: str) -> Optional[str]:
 """
 [C: tests/test_diff_viewer.py:test_get_line_color]
 """
 if   line.startswith("+"):  return "green"
 elif line.startswith("-"):  return "red"
 elif line.startswith("@@"): return "cyan"
 return None

def apply_patch_to_file(patch_text: str, base_dir: str = ".") -> Tuple[bool, str]:
 """
 [C: src/gui_2.py:App._apply_pending_patch, tests/test_diff_viewer.py:test_apply_patch_simple, tests/test_diff_viewer.py:test_apply_patch_with_context]
 """
 diff_files = parse_diff(patch_text)
 if not diff_files:
  return False, "No valid diff found"
 
 results = []
 for df in diff_files:
  file_path = Path(base_dir) / df.old_path
  if not file_path.exists():
   results.append(f"File not found: {file_path}")
   continue
  
  try:
   with open(file_path, "r", encoding="utf-8") as f:
    original_lines = f.read().splitlines(keepends=True)
   
   new_lines = original_lines.copy()
   offset    = 0
   
   for hunk in df.hunks:
    hunk_old_start = hunk.old_start - 1
    hunk_old_count = hunk.old_count
    
    replace_start = hunk_old_start + offset
    replace_count = hunk_old_count
    
    hunk_new_content: List[str] = []
    for line in hunk.lines:
     if line.startswith("+") and not line.startswith("+++"): 
      hunk_new_content.append(line[1:] + "\n")
     elif line.startswith(" ") or (line and not line.startswith(("-", "+", "@@"))):
      hunk_new_content.append(line + "\n")
    
    new_lines = new_lines[:replace_start] + hunk_new_content + new_lines[replace_start + replace_count:]
    offset   += len(hunk_new_content) - replace_count
   
   with open(file_path, "w", encoding="utf-8", newline="") as f:
    f.writelines(new_lines)
   
   results.append(f"Patched: {file_path}")
  except Exception as e:
   return False, f"Error patching {file_path}: {e}"
 
 return True, "\n".join(results)