"""Config validation, review workflow execution, and result aggregation.

Heavy imports live here at the top level — this module is only imported
after argument parsing (and --help) has completed.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import os
import sys
import time
from collections.abc import Callable

from rich.console import Console

from code_reviewer import __version__
from code_reviewer.adaptors import IssueHandler, RepositoryHandler
from code_reviewer.adaptors.factory import create_platform_handlers
from code_reviewer.config import Config, Platform
from code_reviewer.config_file import _run_interactive_setup, load_config_file, validate_config_file
from code_reviewer.errors import (
    CLIError,
    ErrorType,
    NotFound,
    PlatformError,
    PostingError,
    ReviewateError,
    error_type_for_exception,
)
from code_reviewer.explorer.builder import build_code_explorer
from code_reviewer.logging_config import BaseLogger, setup_logging
from code_reviewer.output import ProgressTracker, emit_error, emit_result, print_result_summary
from code_reviewer.utils import format_diff_with_line_numbers
from code_reviewer.workflows import run_summary_workflow
from code_reviewer.workflows.context import LinkedRepoInfo
from code_reviewer.workflows.review import ReviewWorkflow, WorkflowResult
from code_reviewer.workflows.summary import SummaryResult

# Initialize module logger wrapper
logger = BaseLogger("main", "runner")


def _load_and_validate_config(
    args: argparse.Namespace,
    file_config: dict | None,
) -> tuple[Config, str, str]:
    """Load config, validate, and verify API keys.

    Returns (config, review_provider, utility_provider).
    Raises SystemExit(1) on validation or API key failure.
    """
    has_model_env = os.getenv("REVIEW_AGENT_MODEL") or os.getenv("UTILITY_AGENT_MODEL")
    has_model_cli = args.review_model or args.utility_model

    if file_config and not has_model_env and not has_model_cli:
        errors = validate_config_file(file_config)
        if errors:
            _err_console = Console(stderr=True, highlight=False)
            _err_console.print("[bold red]Invalid config file (~/.reviewate/config.toml):[/]")
            for err in errors:
                _err_console.print(f"  [red]{err}[/]")
            _err_console.print("[dim]Fix the file or delete it to re-run setup.[/]")
            raise SystemExit(1) from None

    if not file_config and not has_model_env and not has_model_cli and sys.stdin.isatty():
        file_config = _run_interactive_setup()

    config = Config.from_env(
        review_model=args.review_model,
        utility_model=args.utility_model,
        file_config=file_config,
    )
    if args.review_model:
        logger.info(f"Using CLI override for review model: {args.review_model}")
    if args.utility_model:
        logger.info(f"Using CLI override for utility model: {args.utility_model}")

    # Early check: verify API keys are set for all providers in use
    try:
        review_provider = config.get_agent_provider("review_agent")
        config.get_api_key(review_provider)
    except ReviewateError as e:
        logger.error(str(e))
        raise SystemExit(1) from None
    try:
        utility_provider = config.get_agent_provider("style_agent")
        if utility_provider != review_provider:
            config.get_api_key(utility_provider)
    except ReviewateError as e:
        logger.error(str(e))
        raise SystemExit(1) from None

    return config, review_provider, utility_provider


async def _run_review_workflow(
    *,
    args: argparse.Namespace,
    config: Config,
    repository: RepositoryHandler,
    issue_handler: IssueHandler,
    platform: Platform,
    linked_repos_input: list[LinkedRepoInfo] | None,
    team_guidelines: str | None,
    on_status: Callable[[str], None] | None,
    on_step_done: Callable[[str], None] | None,
) -> WorkflowResult:
    """Fetch PR, build code explorer, and run the review workflow.

    Raises CLIError on handled failures (error already emitted to user).
    """
    if on_status:
        on_status("Fetching PR...")

    try:
        merge_request = await repository.fetch_merge_request(args.repo, args.pr)
    except NotFound:
        msg = f"Pull request #{args.pr} not found in {args.repo}"
        emit_error(ErrorType.NOT_FOUND, msg)
        raise CLIError(msg) from None
    except PlatformError as e:
        emit_error(error_type_for_exception(e), str(e))
        raise CLIError(str(e)) from None

    merge_request.diffs = format_diff_with_line_numbers(merge_request.diffs)

    # Fetch user guidelines and build code explorer in parallel
    async def _fetch_guidelines() -> str:
        for filename in ["CLAUDE.md", "AGENTS.md"]:
            content = await repository.fetch_file(args.repo, merge_request.target_branch, filename)
            if content:
                logger.info(f"Found user guidelines in {filename}")
                return content
        logger.info("No user guidelines found (CLAUDE.md or AGENTS.md)")
        return ""

    if on_status:
        on_status("Building code explorer...")

    try:
        user_guidelines, (code_explorer, linked_repos_info) = await asyncio.gather(
            _fetch_guidelines(),
            build_code_explorer(
                config,
                repository,
                args.repo,
                merge_request.source_branch,
                linked_repos=linked_repos_input,
            ),
        )
    except NotFound:
        msg = (
            f"Branch '{merge_request.source_branch}' not found in {args.repo}"
            " — it may have been deleted"
        )
        emit_error(ErrorType.NOT_FOUND, msg)
        raise CLIError(msg) from None
    except PlatformError as e:
        emit_error(error_type_for_exception(e), str(e))
        raise CLIError(str(e)) from None

    if code_explorer is None:
        logger.error("Failed to build code explorer — cannot proceed with review")
        msg = "Failed to build code explorer from source branch"
        emit_error(ErrorType.EXPLORER_ERROR, msg)
        raise CLIError(msg) from None

    workflow = ReviewWorkflow(config)

    if args.dry_run:
        return await workflow.review(
            merge_request=merge_request,
            platform=platform,
            code_explorer=code_explorer,
            user_guidelines=user_guidelines,
            issue_handler=issue_handler,
            repository=repository,
            debug=args.debug,
            linked_repos=linked_repos_info,
            team_guidelines=team_guidelines,
            on_status=on_status,
            on_step_done=on_step_done,
        )
    else:
        return await workflow.run(
            merge_request=merge_request,
            platform=platform,
            code_explorer=code_explorer,
            repository=repository,
            issue_handler=issue_handler,
            user_guidelines=user_guidelines,
            debug=args.debug,
            linked_repos=linked_repos_info,
            team_guidelines=team_guidelines,
            on_status=on_status,
            on_step_done=on_step_done,
        )


def _aggregate_results(
    results: list[tuple[str, WorkflowResult | SummaryResult]],
) -> dict[str, object]:
    """Build result data dict from workflow results."""
    token_usage = {
        "input_tokens": 0,
        "output_tokens": 0,
        "total_tokens": 0,
        "cached_tokens": 0,
    }
    per_agent_usage: dict[str, dict] = {}
    review_comments: list[str] = []
    review_objects: list[dict] = []
    workflows_list: list[dict] = []

    for _name, result in results:
        if result.total_usage:
            usage = result.total_usage
            token_usage["input_tokens"] += usage.input_tokens
            token_usage["output_tokens"] += usage.output_tokens
            token_usage["total_tokens"] += usage.total_tokens
            token_usage["cached_tokens"] += usage.cached_tokens

        if isinstance(result, WorkflowResult):
            per_agent_usage.update(result.per_agent_usage or {})
            review_comments.extend(result.review_comments or [])
            review_objects.extend(result.review_objects or [])
            workflows_list.append(
                {
                    "name": "review",
                    "issues_found": len(result.issues_found) if result.issues_found else 0,
                }
            )
        elif isinstance(result, SummaryResult):
            workflows_list.append(
                {
                    "name": "summary",
                    "summary_length": len(result.summary_body) if result.summary_body else 0,
                }
            )

    return {
        "status": "success",
        "workflows": workflows_list,
        "token_usage": token_usage,
        "per_agent_usage": per_agent_usage,
        "review_comments": review_comments,
        "review_objects": review_objects,
    }


async def run(args: argparse.Namespace) -> int:
    """Run the review/summary workflows. Called after arg parsing is done."""
    start_time = time.time()

    # Setup logging: containers set LOG_LEVEL explicitly; TTY uses the progress
    # tracker as sole UI so we suppress all log output (CRITICAL).
    is_tty = sys.stderr.isatty()
    log_level = os.getenv("LOG_LEVEL", "CRITICAL" if is_tty else "INFO")
    setup_logging(log_level, debug=args.debug)

    # Set global flag for disabling thinking (checked by base agent)
    if args.disable_thinking:
        os.environ["DISABLE_THINKING"] = "1"
        logger.info("Extended thinking disabled (non-thinking model mode)")

    try:
        logger.info("=" * 60)
        logger.info("REVIEWATE_START")
        logger.info("=" * 60)
        logger.info(
            f"Starting Reviewate CLI - repo={args.repo} pr={args.pr} platform={args.platform}"
        )

        # Load config file (interactive setup on first run if needed)
        file_config = load_config_file()
        config, review_provider, utility_provider = _load_and_validate_config(args, file_config)

        logger.debug(f"Configuration loaded: platform={args.platform} repo={args.repo}")

        # Create platform adaptors
        repository, issue_handler = create_platform_handlers(args.platform, config)

        # Determine platform
        platform = Platform.GITHUB if args.platform == "github" else Platform.GITLAB

        # Parse linked repositories from env var (set by container backend)
        linked_repos_input: list[LinkedRepoInfo] | None = None
        linked_repos_env = os.getenv("LINKED_REPOS")
        if linked_repos_env:
            try:
                linked_repos_input = json.loads(linked_repos_env)
                logger.info(f"Loaded {len(linked_repos_input)} linked repos from env")
            except json.JSONDecodeError:
                logger.warning("Failed to parse LINKED_REPOS env var")

        # Parse team guidelines from env var (set by container backend)
        team_guidelines = os.getenv("TEAM_GUIDELINES")
        if team_guidelines:
            logger.info("Loaded team guidelines from env")

        results: list[tuple[str, WorkflowResult | SummaryResult]] = []

        # Progress tracker for TTY mode (spinner + step log)
        tracker: ProgressTracker | None = None
        _exit_error: str | None = None
        on_status: Callable[[str], None] | None = None
        on_step_done: Callable[[str], None] | None = None

        if is_tty:
            workflows_label = " + ".join(
                f for f, enabled in [("review", args.review), ("summary", args.summary)] if enabled
            )
            tracker = ProgressTracker(
                repo=args.repo,
                pr=args.pr,
                workflows=workflows_label,
                review_model=f"{review_provider}/{config.get_agent_model('review_agent')}",
                utility_model=f"{utility_provider}/{config.get_agent_model('style_agent')}",
                version=__version__,
            )
            tracker.start()
            on_status = tracker.step
            on_step_done = tracker.done

        try:
            # Run summary workflow first (faster, gives quick feedback)
            if args.summary:
                logger.info("Starting summary workflow")
                if on_status:
                    on_status("Running summary workflow...")

                summary_result = await run_summary_workflow(
                    config=config,
                    repository=repository,
                    issue_handler=issue_handler,
                    repo=args.repo,
                    merge_id=args.pr,
                    dry_run=args.dry_run,
                    on_status=on_status,
                    on_step_done=on_step_done,
                )
                results.append(("Summary", summary_result))

            # Run review workflow if requested
            if args.review:
                logger.info("Starting review workflow")
                review_result = await _run_review_workflow(
                    args=args,
                    config=config,
                    repository=repository,
                    issue_handler=issue_handler,
                    platform=platform,
                    linked_repos_input=linked_repos_input,
                    team_guidelines=team_guidelines,
                    on_status=on_status,
                    on_step_done=on_step_done,
                )
                results.append(("Review", review_result))
        except CLIError as e:
            _exit_error = str(e)
            return 1
        except Exception as e:
            _exit_error = str(e)
            raise
        finally:
            if tracker is not None:
                if _exit_error:
                    tracker.fail(_exit_error)
                else:
                    tracker.finish()

        result_data = _aggregate_results(results)
        print_result_summary(result_data)
        emit_result(result_data, include_reviews=args.dry_run)
        return 0

    except KeyboardInterrupt:
        total_duration = time.time() - start_time
        logger.warning(f"REVIEWATE_INTERRUPTED user_cancelled=true duration={total_duration:.2f}s")
        emit_error(ErrorType.INTERRUPTED, "User cancelled the operation")
        return 1
    except PostingError as e:
        total_duration = time.time() - start_time
        logger.error(
            f"REVIEWATE_ERROR error_type=PostingError message={str(e)} duration={total_duration:.2f}s"
        )
        emit_error(ErrorType.POST_FAILED, str(e))
        return 1
    except Exception as e:
        total_duration = time.time() - start_time
        logger.error(
            f"REVIEWATE_ERROR error_type={type(e).__name__} message={str(e)} duration={total_duration:.2f}s"
        )
        if args.debug:
            logger.exception("Full traceback:")
        emit_error(error_type_for_exception(e), str(e))
        return 1
