"""
File processor with change detection.
Copyright (c) 2025-2026 Greg Smethells. All rights reserved.
See the accompanying AUTHORS file for a complete list of authors.
This file is subject to the terms and conditions defined in LICENSE.
"""

from .analyzer import FileAnalyzer
from .rules import BlankLineRuleEngine
from .config import config

class FileProcessor:
  """Handles file processing with change detection"""

  @staticmethod
  def processFile(filepath, checkOnly=False, returnDetails=False):
    """Process file and return True if changes were needed
    :param filepath: Path to file to process
    :type filepath: Path
    :param checkOnly: If True, only check if changes needed without writing
    :type checkOnly: bool
    :param returnDetails: If True, return (changed, summary) tuple
    :type returnDetails: bool
    :rtype: bool or tuple[bool, str]
    """
    try:
      with open(filepath, encoding='utf-8') as f:
        originalLines = f.readlines()
    except UnicodeDecodeError as e:
      print(f'Encoding error reading {filepath}: {e}. Try specifying encoding or convert to UTF-8.')
      return False
    except FileNotFoundError:
      print(f'File not found: {filepath}')
      return False
    except PermissionError:
      print(f'Permission denied reading {filepath}')
      return False
    except Exception as e:
      print(f'Unexpected error reading {filepath}: {e}')
      return False

    # Pass 1: Analyze file structure
    analyzer = FileAnalyzer()
    statements = analyzer.analyzeFile(originalLines)

    # Pass 2: Determine blank line placement
    ruleEngine = BlankLineRuleEngine()
    blankLineCounts = ruleEngine.applyRules(statements)

    # Pass 3: Reconstruct file with correct blank line placement
    newLines = FileProcessor._reconstructFile(statements, blankLineCounts, originalLines)

    # Check if content changed
    if newLines == originalLines:
      return (False, None) if returnDetails else False

    # Generate details if requested
    summary = None
    diff = None

    if returnDetails:
      summary = FileProcessor._generateChangeSummary(originalLines, newLines)
      diff = FileProcessor._generateDiff(originalLines, newLines, filepath)

    # Handle check-only mode
    if checkOnly:
      if returnDetails:
        return (True, (summary, diff))
      else:
        return True

    # Write changes to file atomically
    import tempfile
    import os

    try:
      # Write to temporary file first
      tempFile = None

      with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
                                       dir=filepath.parent,
                                       prefix=f'.{filepath.name}.tmp',
                                       delete=False) as f:
        tempFile = f.name

        f.writelines(newLines)

      # Atomically replace original with temporary file
      os.replace(tempFile, filepath)

      if returnDetails:
        return (True, (summary, diff))
      else:
        return True
    except Exception as e:
      # Clean up temp file if it exists
      if tempFile and os.path.exists(tempFile):
        try:
          os.unlink(tempFile)
        except Exception:
          pass

      print(f'Error writing {filepath}: {e}')

      if returnDetails:
        return (False, None)
      else:
        return False

  @staticmethod
  def _reconstructFile(statements, blankLineCounts, originalLines):
    """Reconstruct file content with correct blank line placement
    :param statements: List of parsed statements
    :type statements: list[Statement]
    :param blankLineCounts: Number of blank lines to add before each statement
    :type blankLineCounts: list[int]
    :param originalLines: Original file lines to preserve trailing newline behavior
    :type originalLines: list[str]
    :rtype: list[str]
    """
    newLines = []

    for i, stmt in enumerate(statements):
      # Skip existing blank lines - we'll add them back where they should be
      if stmt.isBlank:
        continue

      # Add blank lines before this statement if rules specify them
      if i < len(blankLineCounts):
        for _ in range(blankLineCounts[i]):
          newLines.append('\n')

      # Add the statement content
      newLines.extend(stmt.lines)

    # Preserve original file's trailing newline behavior
    if originalLines and newLines:
      originalEndsWithNewline = originalLines[-1].endswith('\n')
      newEndsWithNewline = newLines[-1].endswith('\n')

      if originalEndsWithNewline and not newEndsWithNewline:
        # Original had trailing newline, new doesn't - add it
        newLines[-1] = newLines[-1] + '\n'
      elif not originalEndsWithNewline and newEndsWithNewline:
        # Original had no trailing newline, new does - remove it
        newLines[-1] = newLines[-1].rstrip('\n')
      else:
        # Trailing newline behavior already matches - no change needed
        pass

    return newLines

  @staticmethod
  def _generateChangeSummary(originalLines, newLines):
    """Generate a summary of changes between original and new lines
    :param originalLines: Original file lines
    :type originalLines: list[str]
    :param newLines: New file lines
    :type newLines: list[str]
    :rtype: str
    """
    originalBlankCount = sum(1 for line in originalLines if line.strip() == '')
    newBlankCount = sum(1 for line in newLines if line.strip() == '')
    diff = newBlankCount - originalBlankCount

    if diff > 0:
      return f'added {diff} blank line{"s" if diff != 1 else ""}'
    elif diff < 0:
      return f'removed {-diff} blank line{"s" if diff != -1 else ""}'
    else:
      return 'rearranged blank lines'

  @staticmethod
  def _generateDiff(originalLines, newLines, filepath):
    """Generate a unified diff showing changes between original and new lines
    :param originalLines: Original file lines
    :type originalLines: list[str]
    :param newLines: New file lines
    :type newLines: list[str]
    :param filepath: Path to file for diff header
    :type filepath: Path
    :rtype: str
    """
    import datetime
    import difflib

    timestamp = datetime.datetime.now().isoformat()
    diff = difflib.unified_diff(
      originalLines,
      newLines,
      fromfile=f'{filepath} (original)',
      tofile=f'{filepath} (formatted)',
      lineterm=''
    )

    return '\n'.join(diff)
