#!/usr/bin/env python3
"""Main orchestrator for gar: loads diff, filters files, runs context-based code review.

This is the brain of gar that coordinates the analysis pipeline.
"""

import logging
import os
from pathlib import Path
from typing import Any, Dict, List, Optional

from dotenv import load_dotenv

from normal.git.git import get_diff, get_staged_status, get_repo_root, GitError
from normal.git.preprocess import get_filtered_files_from_diff
from normal.review.context_engine import run_context_review, run_context_review_from_entries, ReviewResult

# Load environment variables
load_dotenv()

logger = logging.getLogger(__name__)


def run_normal_mode_analysis(
    max_iterations: int = 50,
    review_scope: str = "staged",
    file_list: Optional[List[Path]] = None,
    chunking_mode: str = "fast",
    progress_handle=None,
) -> Dict[str, Any]:
    """Run normal mode analysis pipeline.
    
    Pipeline:
    1. Load staged diff or use provided file list
    2. Split diff into sections and filter out unwanted files
    3. Run context-based sequential review on filtered files
    
    Args:
        max_iterations: Maximum number of review iterations
        review_scope: Review scope selection ("staged" or "all")
        file_list: Files to review when review_scope is "all"
        chunking_mode: Chunking strategy ("fast" or "thinking")
        
    Returns:
        dict with analysis results
    """
    # Input validation
    if review_scope not in ("staged", "all"):
        return _result(
            status='invalid_input',
            message=f'Invalid review_scope: {review_scope}.',
        )

    if chunking_mode not in ("fast", "thinking"):
        return _result(
            status='invalid_input',
            message=f'Invalid chunking_mode: {chunking_mode}.',
        )
    
    if not isinstance(max_iterations, int) or max_iterations <= 0:
        logger.error(f"Invalid max_iterations value: {max_iterations}")
        return _result(
            status='invalid_input',
            message=f'Invalid max_iterations: {max_iterations}. Must be a positive integer.',
        )
    
    # Check environment variables
    model_url = os.getenv("GAR_LLM_BASE_URL")
    model_name = os.getenv("GAR_MODEL")
    
    if not model_url:
        logger.error("MODEL_URL environment variable not set")
        return _result(
            status='config_error',
            message='MODEL_URL not configured. Please set MODEL_URL in .env file.',
        )
    
    if not model_name:
        logger.warning("MODEL_NAME not set, using default: qwen3-coder:30b")
    
    try:
        # [DEBUG LOG] 시작 정보
        logger.info(f"[DEBUG] run_normal_mode_analysis started: review_scope={review_scope}, chunking_mode={chunking_mode}, file_list={len(file_list) if file_list else None}")
        print(f"[DEBUG] run_normal_mode_analysis started: review_scope={review_scope}, chunking_mode={chunking_mode}, file_list={len(file_list) if file_list else None}")
        
        existing_files: List[str] = []

        if review_scope == "staged":
            # 1. Load staged diff
            logger.info("Loading staged diff...")
            try:
                diff = get_diff(staged=True, color=False)
            except GitError as e:
                logger.error(f"Git operation failed: {e}")
                return _result(
                    status='git_error',
                    message=f'Git error: {str(e)}. Make sure you are in a git repository.',
                )

            if not diff:
                return _result(
                    status='no_changes',
                    message='No staged changes found.',
                )

            # 2. Filter files from diff
            logger.info("Filtering diff sections...")
            try:
                filtered_files = get_filtered_files_from_diff(diff)
                logger.info(f"Filtered files: {filtered_files}")
            except Exception as e:
                logger.error(f"Failed to filter diff: {e}")
                return _result(
                    status='filter_error',
                    message=f'Failed to process diff: {str(e)}',
                )

            if not filtered_files:
                return _result(
                    status='all_filtered',
                    message='All files were filtered out (binary/build/minified).',
                )

            # 3. Get absolute paths
            try:
                repo_root = Path(get_repo_root())
            except (GitError, Exception) as e:
                logger.warning(f"Could not get repo root: {e}. Using current directory.")
                repo_root = Path.cwd()

            logger.info(f"Repo root: {repo_root}")

            for f in filtered_files:
                try:
                    abs_path = repo_root / f
                    if abs_path.exists():
                        existing_files.append(str(abs_path))
                    else:
                        logger.debug(f"File not found: {abs_path}")
                except (OSError, ValueError) as e:
                    logger.warning(f"Invalid file path '{f}': {e}")
                    continue
        else:
            logger.info("Collecting all project files...")
            if not file_list:
                return _result(
                    status='invalid_input',
                    message='No files provided for full project review.',
                )

            for f in file_list:
                try:
                    abs_path = Path(f).resolve()
                    if abs_path.exists():
                        existing_files.append(str(abs_path))
                    else:
                        logger.debug(f"File not found: {abs_path}")
                except (OSError, ValueError) as e:
                    logger.warning(f"Invalid file path '{f}': {e}")
                    continue
        
        if not existing_files:
            return _result(
                status='no_existing',
                message='No files found to analyze.',
            )
        
        logger.info(f"Found {len(existing_files)} files to analyze")
        print(f"[DEBUG] Found {len(existing_files)} files to analyze: {existing_files[:5]}{'...' if len(existing_files) > 5 else ''}")
        
        # 4. Run context-based review
        logger.info(f"Starting context-based review (max {max_iterations} iterations, mode: {chunking_mode})...")
        print(f"[DEBUG] Starting context-based review: max_iterations={max_iterations}, chunking_mode={chunking_mode}, files={len(existing_files)}")
        try:
            memory, results = run_context_review(
                file_paths=existing_files,
                max_iterations=max_iterations,
                chunking_mode=chunking_mode,
                progress_handle=progress_handle,
            )
        except Exception as e:
            logger.error(f"Review process failed: {e}")
            return _result(
                status='review_error',
                message=f'Code review failed: {str(e)}',
            )
        
        # 5. Build result
        successful = sum(1 for r in results if r.success)
        failed = len(results) - successful
        
        return {
            'status': 'success',
            'message': f'Analysis complete. {successful} chunks processed, {failed} failed.',
            'files_analyzed': existing_files,
            'summary_count': len(memory.filecards),
            'findings_count': len(memory.findings),
            'hotspots': memory.repo_summary.hotspots,
            'filecards': list(memory.filecards.values()),
            'findings': memory.findings,
            'repo_summary': memory.repo_summary,
            'review_results': results,
        }
        
    except KeyboardInterrupt:
        logger.info("Analysis interrupted by user")
        return _result(
            status='interrupted',
            message='Analysis interrupted by user.',
        )
    except Exception as e:
        logger.error(f"Unexpected error during analysis: {e}", exc_info=True)
        return _result(
            status='error',
            message=f'Unexpected error: {str(e)}',
        )


def run_normal_mode_from_input_context(input_context: Dict[str, Any], max_iterations: int = 50) -> Dict[str, Any]:
    """Run normal mode using InputContext."""
    if not input_context:
        return _result(status='invalid_input', message='InputContext is empty.')

    file_entries = input_context.get("files", [])
    if not file_entries:
        return _result(status='no_changes', message='No staged changes found.')

    chunking_mode = input_context.get("chunking_mode", "fast")
    progress_handle = input_context.get("_ui_handle")

    try:
        memory, results = run_context_review_from_entries(
            file_entries=file_entries,
            max_iterations=max_iterations,
            chunking_mode=chunking_mode,
            progress_handle=progress_handle,
        )
    except Exception as e:
        logger.error(f"Review process failed: {e}")
        return _result(status='review_error', message=f'Code review failed: {str(e)}')

    successful = sum(1 for r in results if r.success)
    failed = len(results) - successful
    files_analyzed = [entry.get("path") for entry in file_entries if entry.get("path")]

    return {
        'status': 'success',
        'message': f'Analysis complete. {successful} chunks processed, {failed} failed.',
        'files_analyzed': files_analyzed,
        'summary_count': len(memory.filecards),
        'findings_count': len(memory.findings),
        'hotspots': memory.repo_summary.hotspots,
        'filecards': list(memory.filecards.values()),
        'findings': memory.findings,
        'repo_summary': memory.repo_summary,
        'review_results': results,
    }


def _result(status: str, message: str) -> Dict[str, Any]:
    """Create a result dict with default values."""
    return {
        'status': status,
        'message': message,
        'files_analyzed': [],
        'summary_count': 0,
        'findings_count': 0,
        'hotspots': [],
        'filecards': [],
        'findings': [],
        'repo_summary': None,
        'review_results': [],
    }


