"""Push workflow core - orchestrator and utilities."""

import sys
import time
from typing import Dict, List, Any, Optional
from pathlib import Path

import click

from goal.git_ops import run_git, get_staged_files, get_diff_content, get_diff_stats
from goal.project_bootstrap import detect_project_types_deep, bootstrap_project
from goal.toml_validation import check_pyproject_toml
from goal.push.stages import (
    get_commit_message, enforce_quality_gates, handle_single_commit,
    handle_split_commits, handle_version_sync, get_version_info,
    handle_changelog, run_test_stage, create_tag, push_to_remote,
    handle_publish, handle_dry_run
)


def run_git_local(*args, **kwargs) -> Any:
    """Local wrapper for run_git to avoid import issues."""
    return run_git(*args, **kwargs)


def show_workflow_preview(files, stats, current_version, new_version, commit_msg, commit_body, markdown, ctx_obj) -> None:
    """Show workflow preview for interactive mode."""
    total_adds = sum(s[0] for s in stats.values())
    total_dels = sum(s[1] for s in stats.values())
    denom = (total_adds + total_dels) or 1
    deletion_pct = int((total_dels / denom) * 100)
    net = total_adds - total_dels
    
    if markdown or ctx_obj.get('markdown'):
        click.echo(f"\n## GOAL Workflow Preview\n")
        click.echo(f"- **Files:** {len(files)} (+{total_adds}/-{total_dels} lines, NET {net}, {deletion_pct}% churn deletions)")
        click.echo(f"- **Version:** {current_version} → {new_version}")
        click.echo(f"- **Commit:** `{commit_msg}`")
        if commit_body:
            click.echo(f"\n### Commit Body\n```\n{commit_body}\n```")
    else:
        click.echo(click.style("\n=== GOAL Workflow ===", fg='cyan', bold=True))
        click.echo(f"Will commit {len(files)} files (+{total_adds}/-{total_dels} lines, NET {net}, {deletion_pct}% churn deletions)")
        click.echo(f"Version bump: {current_version} -> {new_version}")
        click.echo(f"Commit message: {click.style(commit_msg, fg='green')}")
        if commit_body:
            click.echo(click.style("\nCommit body (preview):", fg='cyan'))
            click.echo(commit_body)


def output_final_summary(
    ctx_obj: Dict[str, Any],
    markdown: bool,
    project_types: List[str],
    files: List[str],
    stats: Dict,
    current_version: str,
    new_version: str,
    commit_msg: str,
    commit_body: Optional[str],
    test_exit_code: int,
    publish_success: bool,
    no_tag: bool
) -> None:
    """Output final summary in markdown format if requested."""
    if not (markdown or ctx_obj.get('markdown')):
        return
    
    from goal.formatter import format_push_result
    
    success_emoji = "🎉" if test_exit_code == 0 and publish_success else "✅"
    click.echo(click.style(f"\n{success_emoji} Process completed successfully!", fg='green', bold=True))
    
    actions = [
        "Detected project types",
        "Staged changes",
        "Ran tests" if test_exit_code == 0 else "Tests failed but continued",
        "Committed changes",
        f"Updated version to {new_version}",
        "Updated changelog",
        f"Created tag v{new_version}" if not no_tag else "Skipped tag creation",
        "Pushed to remote" if not no_tag else "Pushed to remote without tags",
    ]
    if publish_success:
        actions.append(f"Published version {new_version}")
    else:
        actions.append("Publish failed or skipped")
    
    md_output = format_push_result(
        project_types=project_types,
        files=files,
        stats=stats,
        current_version=current_version,
        new_version=new_version,
        commit_msg=commit_msg,
        commit_body=commit_body,
        test_result="Tests passed" if test_exit_code == 0 else "Tests failed but continued",
        test_exit_code=test_exit_code,
        actions=actions
    )
    click.echo("\n" + md_output)


class PushContext:
    """Context object wrapper for push command."""
    
    def __init__(self, ctx_obj: Dict[str, Any]):
        self.obj = ctx_obj
    
    def get(self, key: str, default=None) -> Any:
        return self.obj.get(key, default)


def execute_push_workflow(
    ctx_obj: Dict[str, Any],
    bump: str,
    no_tag: bool,
    no_changelog: bool,
    no_version_sync: bool,
    message: Optional[str],
    dry_run: bool,
    yes: bool,
    markdown: bool,
    split: bool,
    ticket: Optional[str],
    abstraction: Optional[str],
    todo: bool,
    force: bool = False,
    model: Optional[str] = None,
    api_key: Optional[str] = None
) -> None:
    """Execute the complete push workflow."""
    
    # Early TOML validation for clear error messages
    if not dry_run:
        toml_error = check_pyproject_toml()
        if toml_error:
            click.echo(click.style(toml_error, fg='red', bold=True), err=True)
            click.echo(click.style("\nFix the TOML syntax error and try again.", fg='yellow'), err=True)
            sys.exit(1)
    
    # Start timing
    start_time = time.time()
    
    # Initialize context
    _initialize_context(ctx_obj, bump, message, yes, markdown)
    
    # Get updated yes value from context (includes -a flag)
    yes = ctx_obj['yes']
    
    # Detect and bootstrap projects
    project_types = _detect_and_bootstrap_projects(ctx_obj, dry_run, yes)
    
    # Stage all changes
    if not dry_run:
        run_git('add', '-A')
    
    # Get staged files
    files = get_staged_files()
    if not files or files == ['']:
        _handle_no_changes(ctx_obj, project_types, dry_run, markdown)
        return
    
    # Validate staged files
    _validate_staged_files(ctx_obj, dry_run, force)
    
    # Get diff content and stats
    diff_content = get_diff_content()
    stats = get_diff_stats()
    
    # Generate commit message
    commit_title, commit_body, detailed_result = get_commit_message(
        ctx_obj, files, diff_content, message, ticket, abstraction
    )
    
    if not commit_title:
        click.echo(click.style("No changes to commit.", fg='yellow'))
        return
    
    commit_msg = commit_title
    
    # Get version info
    current_version, new_version = get_version_info()
    
    # Enforce quality gates
    if not message and detailed_result and detailed_result.get('enhanced'):
        total_adds = sum(s[0] for s in stats.values())
        total_dels = sum(s[1] for s in stats.values())
        commit_msg = enforce_quality_gates(
            ctx_obj, commit_msg, detailed_result, files, total_adds, total_dels,
            ctx_obj['yes'], markdown
        )
    
    # Handle dry run
    if dry_run:
        handle_dry_run(ctx_obj, project_types, files, stats, current_version, new_version,
                      commit_msg, commit_body, detailed_result, split, ticket, bump,
                      no_version_sync, no_changelog, no_tag, markdown)
        return
    
    # Interactive workflow preview
    if not ctx_obj['yes']:
        show_workflow_preview(files, stats, current_version, new_version,
                             commit_msg, commit_body, markdown, ctx_obj)
    
    # Test stage
    test_result, test_exit_code = run_test_stage(
        project_types, ctx_obj['yes'], markdown, ctx_obj, files, stats,
        current_version, new_version, commit_msg, commit_body
    )
    
    # Commit changes
    _handle_commit_phase(ctx_obj, split, message, commit_title, commit_body, commit_msg,
                         files, ticket, new_version, current_version, no_version_sync,
                         no_changelog)
    
    # Create tag
    tag_name = create_tag(new_version, no_tag)
    
    # Push to remote
    from goal.git_ops import get_remote_branch
    branch = get_remote_branch()
    push_to_remote(branch, tag_name, no_tag, ctx_obj['yes'])
    
    # Publish
    publish_success = handle_publish(project_types, new_version, ctx_obj['yes'])
    
    # Calculate elapsed time
    elapsed = time.time() - start_time
    ctx_obj['_elapsed_time'] = elapsed
    
    # Final summary
    output_final_summary(ctx_obj, markdown, project_types, files, stats, current_version,
                        new_version, commit_msg, commit_body, test_exit_code,
                        publish_success, no_tag)
    
    # Print timing info
    click.echo(click.style(f"\n⏱️  Total time: {elapsed:.1f}s", fg='cyan'))
    
    # Update AI cost badges in README
    _update_cost_badges(ctx_obj, new_version)


def _initialize_context(ctx_obj: Dict[str, Any], bump: str, message: Optional[str],
                       yes: bool, markdown: bool) -> None:
    """Initialize context with common values."""
    # Use yes from context (includes -a from main command) or local --yes flag
    yes = ctx_obj.get('yes', False) or yes
    ctx_obj['yes'] = yes
    ctx_obj['bump'] = bump
    ctx_obj['message'] = message
    ctx_obj['markdown'] = markdown or ctx_obj.get('markdown', False)


def _detect_and_bootstrap_projects(ctx_obj: Dict[str, Any], dry_run: bool,
                                 yes: bool) -> List[str]:
    """Detect project types and bootstrap environments."""
    # Detect project types (lazy import to avoid circular dependency)
    from goal.cli.version import detect_project_types
    project_types = detect_project_types()
    if project_types and not dry_run:
        click.echo(f"Detected project types: {click.style(', '.join(project_types), fg='cyan')}")
    
    # Bootstrap project environments
    if not dry_run and project_types:
        deep_detected = detect_project_types_deep()
        for ptype, dirs in deep_detected.items():
            for pdir in dirs:
                bootstrap_project(pdir, ptype, yes=yes)
    
    return project_types


def _handle_no_changes(ctx_obj: Dict[str, Any], project_types: List[str],
                      dry_run: bool, markdown: bool) -> None:
    """Handle case when no changes are staged."""
    if markdown or ctx_obj.get('markdown'):
        from goal.cli.version import get_current_version
        from goal.formatter import format_push_result
        
        current_version = get_current_version()
        md_output = format_push_result(
            project_types=project_types or [],
            files=[],
            stats={},
            current_version=current_version,
            new_version=current_version,
            commit_msg="(none)",
            commit_body="No staged changes detected.",
            test_result="Not executed",
            test_exit_code=0,
            actions=["Detected project types"],
            error="No changes to commit"
        )
        click.echo(md_output)
    else:
        click.echo(click.style("No changes to commit.", fg='yellow'))


def _validate_staged_files(ctx_obj: Dict[str, Any], dry_run: bool, force: bool) -> None:
    """Validate staged files for security issues."""
    if not dry_run and not force:
        from goal.validators import validate_staged_files
        try:
            validate_staged_files(ctx_obj.get('config'))
        except Exception as e:
            click.echo(click.style(f"\n❌ Validation Error: {str(e)}", fg='red', bold=True))
            click.echo(click.style("\nFor security reasons, the commit has been blocked.", fg='red'))
            click.echo(click.style("\nTo bypass this check, you can:", fg='yellow'))
            click.echo(click.style("1. Remove the sensitive/large file(s)", fg='yellow'))
            click.echo(click.style("2. Add the file(s) to .gitignore", fg='yellow'))
            click.echo(click.style("3. Use --force to bypass validation (not recommended)", fg='yellow'))
            sys.exit(1)
    elif force and not dry_run:
        click.echo(click.style("⚠️  Security validation bypassed with --force", fg='yellow', bold=True))


def _handle_commit_phase(ctx_obj: Dict[str, Any], split: bool, message: Optional[str],
                        commit_title: str, commit_body: Optional[str], commit_msg: str,
                        files: List[str], ticket: Optional[str], new_version: str,
                        current_version: str, no_version_sync: bool, no_changelog: bool) -> None:
    """Handle the commit phase of the workflow."""
    from goal.cli import confirm
    
    # Commit confirmation
    if not ctx_obj['yes']:
        if not confirm("Commit changes?"):
            click.echo(click.style("  🤖 AUTO: Aborting commit (user chose N)", fg='cyan'))
            click.echo(click.style("Aborted.", fg='red'))
            sys.exit(1)
    else:
        click.echo(click.style("🤖 AUTO: Committing changes (--all mode)", fg='cyan'))
    
    # Handle split commits or single commit
    if split and not message:
        run_git('reset')  # Unstage everything
        handle_split_commits(ctx_obj, files, ticket, new_version, current_version,
                            no_version_sync, no_changelog, ctx_obj['yes'])
    else:
        # Version sync
        user_config = ctx_obj.get('user_config')
        handle_version_sync(new_version, no_version_sync, user_config, ctx_obj['yes'])
        
        # Changelog
        config_dict = (ctx_obj.get('config') or {}).to_dict() if ctx_obj.get('config') else None
        handle_changelog(new_version, files, commit_msg, config_dict, no_changelog)
        
        # Single commit
        handle_single_commit(commit_title, commit_body, commit_msg, message, ctx_obj['yes'])


def _update_cost_badges(ctx_obj: Dict[str, Any], version: str, model: Optional[str] = None, api_key: Optional[str] = None) -> None:
    """Update AI cost badges in README using costs package."""
    try:
        # Lazy import to avoid hard dependency
        # Try new simplified API first (costs >= 0.1.21)
        try:
            from costs import calculate_human_time, update_readme_badge
        except ImportError:
            # Fallback to old API (costs <= 0.1.20)
            from costs.reports import update_readme_badge
            from datetime import datetime
            from collections import defaultdict
            
            def calculate_human_time(commits):
                """Calculate human hours from commit history."""
                if not commits:
                    return 0.0
                daily_commits = defaultdict(lambda: defaultdict(list))
                for commit in commits:
                    date = commit.get('date', '')[:10]
                    author = commit.get('author', 'Unknown')
                    daily_commits[date][author].append(commit)
                total_hours = 0.0
                for date, authors in daily_commits.items():
                    for author, author_commits in authors.items():
                        hours = min(len(author_commits) * 0.5, 8.0)
                        total_hours += hours
                return total_hours
        
        from costs.git_parser import parse_commits, get_repo_stats, get_commit_diff
        from costs.calculator import ai_cost
        
        # Use provided model or default
        model = model or ctx_obj.get('cost_model') or "openrouter/qwen/qwen3-coder-next"
        api_key = api_key or ctx_obj.get('cost_api_key')
        
        # Check if costs tracking is enabled in pyproject.toml
        import tomllib
        config_path = Path("pyproject.toml")
        
        # Default to enabled unless explicitly disabled
        badge_enabled = True
        update_readme_enabled = True
        auto_commit = False
        
        if config_path.exists():
            with open(config_path, "rb") as f:
                config = tomllib.load(f)
            
            tool_costs = config.get("tool", {}).get("costs", {})
            # Only disable if explicitly set to False
            if tool_costs.get("badge") is False:
                badge_enabled = False
            if tool_costs.get("update_readme") is False:
                update_readme_enabled = False
            auto_commit = tool_costs.get("auto_commit", False)
        
        if not badge_enabled and not update_readme_enabled:
            return
        
        # Get repository statistics and build results
        project_dir = Path(".")
        repo_stats = get_repo_stats(str(project_dir))
        
        # Get all commits with AI detection
        all_commits_data = parse_commits(
            str(project_dir),
            max_count=500,
            ai_only=False,
            full_history=True
        )
        
        # Calculate costs for AI commits
        ai_commits = [c for c in all_commits_data if c[1].get('is_ai', False)]
        
        # Calculate total cost from AI commits
        total_cost = 0.0
        total_commits = len(ai_commits)
        
        if ai_commits:
            for commit_obj, commit_data in ai_commits[:50]:
                try:
                    diff = get_commit_diff(str(project_dir), commit_obj.hexsha)
                    if diff:
                        cost_result = ai_cost(diff, model=model, api_key=api_key)
                        total_cost += cost_result.get('cost', 0.0)
                except Exception:
                    total_cost += 0.15
        
        if total_cost == 0 and len(all_commits_data) > 0:
            total_cost = len(all_commits_data) * 0.15
            total_commits = len(all_commits_data)
        
        # Calculate human time
        all_commits_list = [
            {"date": c[0].committed_datetime.isoformat(), "author": c[0].author.name}
            for c in all_commits_data
        ]
        human_hours = calculate_human_time(all_commits_list)
        
        # Build results structure
        results = {
            "summary": {
                "total_cost": total_cost,
                "total_cost_formatted": f"${total_cost:.4f}",
                "total_commits": total_commits,
                "model": model,
                "version": version,
                "human_time": human_hours,
                "human_cost": human_hours * 100
            }
        }
        
        # Update badge
        success = update_readme_badge(project_dir, results)
        if success:
            click.echo(click.style("✓ Updated AI cost badges in README", fg='green'))
        
        # Optionally commit the badge update
        if auto_commit:
            from goal.git_ops import run_git
            run_git('add', 'README.md')
            run_git('commit', '-m', f'chore: update AI cost badges for v{version}')
                
    except ImportError:
        # costs package not installed, skip silently
        pass
    except Exception as e:
        # Non-critical feature, log error only in verbose mode
        if ctx_obj.get('verbose'):
            click.echo(click.style(f"⚠ Could not update cost badges: {e}", fg='yellow'))
