"""Project environment bootstrapping and test scaffolding.

Detects project types (including 1-level-deep subfolders), ensures environments
are properly initialized (venv, deps), and scaffolds sample tests when missing.
"""

import os
import subprocess
import shutil
import json
from pathlib import Path
from typing import Dict, List, Optional

import click

from goal.project_doctor import diagnose_and_report


# =============================================================================
# Project bootstrap configuration per language/framework
# =============================================================================

PROJECT_BOOTSTRAP = {
    'python': {
        'marker_files': ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt'],
        'env_dir': '.venv',
        'test_dirs': ['tests', 'test'],
        'test_patterns': ['test_*.py', '*_test.py'],
        'dep_install_commands': [
            # Order matters: try pyproject.toml editable install first, then requirements.txt
            {'condition': 'pyproject.toml', 'cmd': '{python} -m pip install -e ".[dev]"', 'fallback': '{python} -m pip install -e .'},
            {'condition': 'requirements.txt', 'cmd': '{python} -m pip install -r requirements.txt'},
            {'condition': 'requirements-dev.txt', 'cmd': '{python} -m pip install -r requirements-dev.txt'},
        ],
        'test_command': 'pytest',
        'test_dep': 'pytest',
        'scaffold_test': '''"""Sample test — auto-generated by goal."""


def test_placeholder():
    """Placeholder test to verify the test setup works."""
    assert True


def test_import():
    """Verify the main package can be imported."""
    import {package_name}  # noqa: F401
''',
    },
    'nodejs': {
        'marker_files': ['package.json'],
        'env_dir': 'node_modules',
        'test_dirs': ['test', 'tests', '__tests__', 'spec'],
        'test_patterns': ['*.test.js', '*.spec.js', '*.test.ts', '*.spec.ts'],
        'dep_install_commands': [
            {'condition': 'yarn.lock', 'cmd': 'yarn install'},
            {'condition': 'pnpm-lock.yaml', 'cmd': 'pnpm install'},
            {'condition': 'package-lock.json', 'cmd': 'npm install'},
            {'condition': 'package.json', 'cmd': 'npm install'},
        ],
        'test_command': 'npm test',
        'test_dep': 'jest',
        'scaffold_test': '''// Sample test — auto-generated by goal.

describe('{package_name}', () => {{
  test('placeholder test', () => {{
    expect(true).toBe(true);
  }});
}});
''',
    },
    'rust': {
        'marker_files': ['Cargo.toml'],
        'env_dir': 'target',
        'test_dirs': ['tests'],
        'test_patterns': ['*.rs'],
        'dep_install_commands': [
            {'condition': 'Cargo.toml', 'cmd': 'cargo build'},
        ],
        'test_command': 'cargo test',
        'test_dep': None,
        'scaffold_test': '''// Sample test — auto-generated by goal.

#[cfg(test)]
mod tests {{
    #[test]
    fn placeholder_test() {{
        assert!(true);
    }}
}}
''',
    },
    'go': {
        'marker_files': ['go.mod'],
        'env_dir': None,
        'test_dirs': ['.'],
        'test_patterns': ['*_test.go'],
        'dep_install_commands': [
            {'condition': 'go.mod', 'cmd': 'go mod download'},
        ],
        'test_command': 'go test ./...',
        'test_dep': None,
        'scaffold_test': '''// Sample test — auto-generated by goal.
package main

import "testing"

func TestPlaceholder(t *testing.T) {{
\tif false {{
\t\tt.Error("placeholder test failed")
\t}}
}}
''',
    },
    'ruby': {
        'marker_files': ['Gemfile', '*.gemspec'],
        'env_dir': 'vendor/bundle',
        'test_dirs': ['spec', 'test'],
        'test_patterns': ['*_spec.rb', 'test_*.rb', '*_test.rb'],
        'dep_install_commands': [
            {'condition': 'Gemfile', 'cmd': 'bundle install'},
        ],
        'test_command': 'bundle exec rspec',
        'test_dep': 'rspec',
        'scaffold_test': '''# Sample test — auto-generated by goal.

RSpec.describe '{package_name}' do
  it 'placeholder test' do
    expect(true).to be true
  end
end
''',
    },
    'php': {
        'marker_files': ['composer.json'],
        'env_dir': 'vendor',
        'test_dirs': ['tests', 'test'],
        'test_patterns': ['*Test.php', 'test_*.php'],
        'dep_install_commands': [
            {'condition': 'composer.json', 'cmd': 'composer install'},
        ],
        'test_command': 'composer test',
        'test_dep': 'phpunit/phpunit',
        'scaffold_test': '''<?php
// Sample test — auto-generated by goal.

use PHPUnit\\Framework\\TestCase;

class PlaceholderTest extends TestCase
{{
    public function testPlaceholder(): void
    {{
        $this->assertTrue(true);
    }}
}}
''',
    },
    'dotnet': {
        'marker_files': ['*.csproj', '*.fsproj', '*.sln'],
        'env_dir': 'bin',
        'test_dirs': ['Tests', 'tests'],
        'test_patterns': ['*Tests.cs', '*Test.cs'],
        'dep_install_commands': [
            {'condition': '*.csproj', 'cmd': 'dotnet restore'},
            {'condition': '*.sln', 'cmd': 'dotnet restore'},
        ],
        'test_command': 'dotnet test',
        'test_dep': None,
        'scaffold_test': '''// Sample test — auto-generated by goal.
using Xunit;

namespace {package_name}.Tests
{{
    public class PlaceholderTest
    {{
        [Fact]
        public void Placeholder_ShouldPass()
        {{
            Assert.True(true);
        }}
    }}
}}
''',
    },
    'java': {
        'marker_files': ['pom.xml', 'build.gradle', 'build.gradle.kts'],
        'env_dir': 'target',
        'test_dirs': ['src/test/java', 'src/test'],
        'test_patterns': ['*Test.java', '*Tests.java'],
        'dep_install_commands': [
            {'condition': 'pom.xml', 'cmd': 'mvn dependency:resolve'},
            {'condition': 'build.gradle', 'cmd': 'gradle dependencies'},
            {'condition': 'build.gradle.kts', 'cmd': 'gradle dependencies'},
        ],
        'test_command': 'mvn test',
        'test_dep': None,
        'scaffold_test': '''// Sample test — auto-generated by goal.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PlaceholderTest {{
    @Test
    void placeholderTest() {{
        assertTrue(true);
    }}
}}
''',
    },
}


# =============================================================================
# Detection helpers
# =============================================================================

def _match_marker(base: Path, pattern: str) -> bool:
    """Check if a marker file/pattern exists under *base*."""
    if '*' in pattern:
        return bool(list(base.glob(pattern)))
    return (base / pattern).exists()


def detect_project_types_deep(root: Optional[Path] = None, max_depth: int = 1) -> Dict[str, List[Path]]:
    """Detect project types in *root* and up to *max_depth* subfolder levels.

    Returns a dict mapping project type → list of directories where it was found.
    """
    if root is None:
        root = Path('.')
    root = root.resolve()

    results: Dict[str, List[Path]] = {}

    dirs_to_scan = [root]
    if max_depth >= 1:
        try:
            for child in sorted(root.iterdir()):
                if child.is_dir() and not child.name.startswith('.'):
                    dirs_to_scan.append(child)
        except PermissionError:
            pass

    for scan_dir in dirs_to_scan:
        for ptype, cfg in PROJECT_BOOTSTRAP.items():
            for marker in cfg['marker_files']:
                if _match_marker(scan_dir, marker):
                    results.setdefault(ptype, []).append(scan_dir)
                    break

    return results


def guess_package_name(project_dir: Path, project_type: str) -> str:
    """Best-effort guess of the package/module name for scaffold templates."""
    project_dir = project_dir.resolve()

    if project_type == 'python':
        # Try pyproject.toml name field
        pyproject = project_dir / 'pyproject.toml'
        if pyproject.exists():
            try:
                import re
                content = pyproject.read_text(errors='ignore')
                m = re.search(r'^name\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
                if m:
                    return m.group(1).replace('-', '_')
            except Exception:
                pass
        # Try setup.py
        setup_py = project_dir / 'setup.py'
        if setup_py.exists():
            try:
                import re
                content = setup_py.read_text(errors='ignore')
                m = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
                if m:
                    return m.group(1).replace('-', '_')
            except Exception:
                pass

    elif project_type == 'nodejs':
        pkg = project_dir / 'package.json'
        if pkg.exists():
            try:
                data = json.loads(pkg.read_text(errors='ignore'))
                name = data.get('name', '')
                return name.split('/')[-1] if '/' in name else name
            except Exception:
                pass

    elif project_type == 'rust':
        cargo = project_dir / 'Cargo.toml'
        if cargo.exists():
            try:
                import re
                content = cargo.read_text(errors='ignore')
                m = re.search(r'^name\s*=\s*"([^"]+)"', content, re.MULTILINE)
                if m:
                    return m.group(1)
            except Exception:
                pass

    elif project_type == 'go':
        gomod = project_dir / 'go.mod'
        if gomod.exists():
            try:
                first_line = gomod.read_text(errors='ignore').split('\n')[0]
                parts = first_line.strip().split()
                if len(parts) >= 2:
                    return parts[1].rsplit('/', 1)[-1]
            except Exception:
                pass

    # Fallback: directory name
    return project_dir.name.replace('-', '_')


# =============================================================================
# Environment bootstrapping
# =============================================================================

def _find_python_bin(project_dir: Path) -> str:
    """Return the best python binary path for a project directory."""
    # Check common venv names in order of preference
    for venv_name in ['.venv', 'venv', 'env']:
        venv_python = project_dir / venv_name / 'bin' / 'python'
        if venv_python.exists():
            return str(venv_python)
    # Check active virtualenv
    venv_env = os.environ.get('VIRTUAL_ENV')
    if venv_env:
        candidate = Path(venv_env) / 'bin' / 'python'
        if candidate.exists():
            return str(candidate)
    # sys.executable
    import sys
    return sys.executable


def ensure_project_environment(project_dir: Path, project_type: str, yes: bool = False) -> bool:
    """Ensure the project environment is properly set up.

    For Python: creates .venv if missing, installs dependencies.
    For Node: runs npm/yarn/pnpm install if node_modules missing.
    For others: runs the appropriate dependency install command.

    Returns True if environment is ready, False on failure.
    """
    cfg = PROJECT_BOOTSTRAP.get(project_type)
    if not cfg:
        return True

    project_dir = project_dir.resolve()
    env_dir = cfg.get('env_dir')

    # --- Python special handling: create venv if missing ---
    if project_type == 'python':
        venv_path = project_dir / '.venv'
        if not venv_path.exists():
            if not yes:
                click.echo(click.style(f"\n⚠  No virtual environment found in {project_dir}", fg='yellow'))
                if not click.confirm(click.style("Create .venv and install dependencies?", fg='cyan'), default=True):
                    return True  # user declined, not a failure
            click.echo(click.style(f"  Creating .venv in {project_dir} ...", fg='cyan'))
            import sys
            result = subprocess.run(
                [sys.executable, '-m', 'venv', str(venv_path)],
                capture_output=True, text=True
            )
            if result.returncode != 0:
                click.echo(click.style(f"  ✗ Failed to create venv: {result.stderr.strip()}", fg='red'))
                return False
            click.echo(click.style("  ✓ Created .venv", fg='green'))

        python_bin = _find_python_bin(project_dir)

        # Upgrade pip
        subprocess.run([python_bin, '-m', 'pip', 'install', '--upgrade', 'pip'],
                       capture_output=True, text=True, cwd=str(project_dir))

        # Install costs package for AI tracking
        _ensure_costs_installed(project_dir, python_bin)

        # Install deps
        for dep_cfg in cfg['dep_install_commands']:
            condition_file = dep_cfg['condition']
            if _match_marker(project_dir, condition_file):
                cmd = dep_cfg['cmd'].format(python=python_bin)
                click.echo(click.style(f"  Installing deps: {cmd}", fg='cyan'))
                result = subprocess.run(
                    cmd, shell=True, cwd=str(project_dir),
                    capture_output=True, text=True
                )
                if result.returncode != 0:
                    # Try fallback if available
                    fallback = dep_cfg.get('fallback')
                    if fallback:
                        cmd = fallback.format(python=python_bin)
                        click.echo(click.style(f"  Retrying: {cmd}", fg='yellow'))
                        result = subprocess.run(
                            cmd, shell=True, cwd=str(project_dir),
                            capture_output=True, text=True
                        )
                    if result.returncode != 0:
                        click.echo(click.style(f"  ⚠  Dependency install had issues (exit {result.returncode})", fg='yellow'))
                        # Not fatal — some deps may be optional
                else:
                    click.echo(click.style("  ✓ Dependencies installed", fg='green'))
                break  # Only run the first matching dep install
        return True

    # --- Generic handling for other project types ---
    if env_dir:
        env_path = project_dir / env_dir
        if env_path.exists():
            return True  # Already set up

    # Check if deps need installing
    needs_install = env_dir and not (project_dir / env_dir).exists()
    if not needs_install:
        # Check if any condition file exists but env is missing
        for dep_cfg in cfg['dep_install_commands']:
            if _match_marker(project_dir, dep_cfg['condition']):
                needs_install = True
                break

    if needs_install:
        if not yes:
            click.echo(click.style(f"\n⚠  Dependencies not installed for {project_type} project in {project_dir}", fg='yellow'))
            if not click.confirm(click.style("Install dependencies?", fg='cyan'), default=True):
                return True

        for dep_cfg in cfg['dep_install_commands']:
            if _match_marker(project_dir, dep_cfg['condition']):
                cmd = dep_cfg['cmd']
                tool = cmd.split()[0]
                if not shutil.which(tool):
                    click.echo(click.style(f"  ⚠  '{tool}' not found in PATH, skipping", fg='yellow'))
                    continue
                click.echo(click.style(f"  Installing deps: {cmd}", fg='cyan'))
                result = subprocess.run(
                    cmd, shell=True, cwd=str(project_dir),
                    capture_output=True, text=True
                )
                if result.returncode == 0:
                    click.echo(click.style("  ✓ Dependencies installed", fg='green'))
                else:
                    click.echo(click.style(f"  ⚠  Install had issues (exit {result.returncode})", fg='yellow'))
                break

    return True


# =============================================================================
# Test scaffolding
# =============================================================================

def find_existing_tests(project_dir: Path, project_type: str) -> List[Path]:
    """Find existing test files for the given project type."""
    cfg = PROJECT_BOOTSTRAP.get(project_type)
    if not cfg:
        return []

    found: List[Path] = []
    for test_dir_name in cfg['test_dirs']:
        test_dir = project_dir / test_dir_name
        if not test_dir.exists():
            continue
        for pattern in cfg['test_patterns']:
            found.extend(test_dir.rglob(pattern))

    return found


def scaffold_test(project_dir: Path, project_type: str, yes: bool = False) -> Optional[Path]:
    """Create a sample test file if no tests exist.

    Returns the path to the created test file, or None if skipped/not needed.
    """
    cfg = PROJECT_BOOTSTRAP.get(project_type)
    if not cfg or not cfg.get('scaffold_test'):
        return None

    existing = find_existing_tests(project_dir, project_type)
    if existing:
        return None  # Tests already exist

    if not yes:
        click.echo(click.style(f"\n⚠  No tests found for {project_type} project in {project_dir}", fg='yellow'))
        if not click.confirm(click.style("Create a sample test file?", fg='cyan'), default=True):
            return None

    package_name = guess_package_name(project_dir, project_type)

    # Determine test directory and file name
    test_dir_name = cfg['test_dirs'][0]
    test_dir = project_dir / test_dir_name

    if project_type == 'python':
        test_file = test_dir / f'test_{package_name}.py'
    elif project_type == 'nodejs':
        test_file = test_dir / f'{package_name}.test.js'
    elif project_type == 'rust':
        test_file = test_dir / 'placeholder_test.rs'
    elif project_type == 'go':
        test_file = project_dir / 'placeholder_test.go'
    elif project_type == 'ruby':
        test_file = test_dir / f'{package_name}_spec.rb'
    elif project_type == 'php':
        test_file = test_dir / 'PlaceholderTest.php'
    elif project_type == 'dotnet':
        test_file = test_dir / 'PlaceholderTest.cs'
    elif project_type == 'java':
        test_file = test_dir / 'PlaceholderTest.java'
    else:
        return None

    if test_file.exists():
        return None

    test_dir.mkdir(parents=True, exist_ok=True)
    content = cfg['scaffold_test'].format(package_name=package_name)
    test_file.write_text(content, encoding='utf-8')

    click.echo(click.style(f"  ✓ Created sample test: {test_file.relative_to(project_dir)}", fg='green'))
    return test_file


# =============================================================================
# High-level orchestration
# =============================================================================

def bootstrap_project(project_dir: Path, project_type: str, yes: bool = False) -> Dict:
    """Full bootstrap: diagnose & fix config, ensure environment, scaffold tests.

    Returns a dict with status info:
        {
            'project_type': str,
            'project_dir': Path,
            'env_ok': bool,
            'tests_found': list[Path],
            'test_created': Path | None,
            'doctor_report': DoctorReport | None,
        }
    """
    result = {
        'project_type': project_type,
        'project_dir': project_dir,
        'env_ok': False,
        'tests_found': [],
        'test_created': None,
        'doctor_report': None,
    }

    # Step 1: Diagnose and auto-fix project configuration issues
    click.echo(click.style(f"  DEBUG: auto_fix={yes}", fg='magenta'))
    result['doctor_report'] = diagnose_and_report(project_dir, project_type, auto_fix=yes)

    # Step 1b: Ensure pfix is installed for auto-fixing errors
    _ensure_pfix_installed(project_dir, yes=yes)

    # Step 2: Ensure environment (venv, deps)
    # Note: For Python projects, this also installs the costs package
    result['env_ok'] = ensure_project_environment(project_dir, project_type, yes=yes)

    # Step 3: Find or scaffold tests
    result['tests_found'] = find_existing_tests(project_dir, project_type)

    if not result['tests_found']:
        result['test_created'] = scaffold_test(project_dir, project_type, yes=yes)
        if result['test_created']:
            result['tests_found'] = [result['test_created']]

    return result


def bootstrap_all_projects(root: Optional[Path] = None, yes: bool = False) -> List[Dict]:
    """Detect all project types (root + 1-level subfolders) and bootstrap each.

    Returns a list of bootstrap result dicts.
    """
    detected = detect_project_types_deep(root)
    if not detected:
        return []

    results = []
    for ptype, dirs in detected.items():
        for project_dir in dirs:
            try:
                rel = project_dir.relative_to(root.resolve() if root else Path('.').resolve())
            except ValueError:
                rel = project_dir
            click.echo(click.style(f"\n📦 Bootstrapping {ptype} project in {rel}", fg='cyan', bold=True))
            res = bootstrap_project(project_dir, ptype, yes=yes)
            results.append(res)

    return results


def _ensure_costs_installed(project_dir: Path, python_bin: str) -> bool:
    """Ensure costs package is installed and configured for AI tracking.
    
    Returns True if costs is ready to use.
    """
    import subprocess
    
    click.echo(click.style(f"  DEBUG: _ensure_costs_installed called for {project_dir}", fg='magenta'))
    
    # Check if costs is already installed
    result = subprocess.run(
        [python_bin, '-c', 'import costs; print(costs.__version__)'],
        capture_output=True, text=True, cwd=str(project_dir)
    )
    
    if result.returncode != 0:
        # Install costs
        click.echo(click.style(f"  Installing costs package...", fg='cyan'))
        install_result = subprocess.run(
            [python_bin, '-m', 'pip', 'install', 'costs>=0.1.20'],
            capture_output=True, text=True, cwd=str(project_dir)
        )
        if install_result.returncode != 0:
            click.echo(click.style(f"  ⚠ Could not install costs package", fg='yellow'))
            return False
        click.echo(click.style("  ✓ Costs package installed", fg='green'))
    else:
        click.echo(click.style(f"  ✓ Costs package already installed ({result.stdout.strip()})", fg='green'))
    
    # Check/add costs config to pyproject.toml
    config_ok = _ensure_costs_config(project_dir)
    
    # Create .env template for API key if not exists
    env_ok = _ensure_env_template(project_dir)
    
    # Generate initial badge in README (append at end, not after first heading)
    click.echo(click.style(f"  Generating AI cost badge...", fg='cyan'))
    
    try:
        # 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
                
                # Group commits by date and author
                daily_commits = defaultdict(lambda: defaultdict(list))
                for commit in commits:
                    date = commit.get('date', '')[:10]  # YYYY-MM-DD
                    author = commit.get('author', 'Unknown')
                    daily_commits[date][author].append(commit)
                
                # Estimate hours: assume each author works ~2 hours per day with commits
                total_hours = 0.0
                for date, authors in daily_commits.items():
                    for author, author_commits in authors.items():
                        # Estimate 30 min per commit, capped at 8 hours per day
                        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
        import json
        
        # Get repository statistics
        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
        # parse_commits returns List[Tuple[Commit, str]] where str is commit message
        ai_indicators = ['🤖', 'AI:', '[AI]', '(AI)', 'automat', 'cascade', 'claude', 'gpt', 'llm']
        ai_commits = [
            c for c in all_commits_data 
            if any(ind in c[1].lower() for ind in ai_indicators)
        ]
        
        # Calculate total cost from AI commits
        total_cost = 0.0
        total_commits = len(ai_commits)
        
        if ai_commits:
            # Get commit diffs and calculate costs
            commits_with_diffs = []
            for commit_obj, commit_data in ai_commits[:50]:  # Limit to first 50 for performance
                try:
                    diff = get_commit_diff(str(project_dir), commit_obj.hexsha)
                    if diff:
                        cost_result = ai_cost(diff, model='openrouter/qwen/qwen3-coder-next')
                        total_cost += cost_result.get('cost', 0.0)
                except Exception:
                    # Estimate based on commit size
                    total_cost += 0.15  # Average cost per AI commit
        
        # If no AI commits detected, estimate from total commits
        if total_cost == 0 and len(all_commits_data) > 0:
            total_cost = len(all_commits_data) * 0.15  # Estimate $0.15 per commit
            total_commits = len(all_commits_data)
        
        # Get model from pyproject.toml or use default
        model = "openrouter/qwen/qwen3-coder-next"
        pyproject = project_dir / 'pyproject.toml'
        if pyproject.exists():
            content = pyproject.read_text()
            if 'default_model' in content:
                import re
                match = re.search(r'default_model\s*=\s*"([^"]+)"', content)
                if match:
                    model = match.group(1)
        
        # 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 expected by update_readme_badge
        results = {
            "summary": {
                "total_cost": total_cost,
                "total_cost_formatted": f"${total_cost:.4f}",
                "total_commits": total_commits,
                "model": model,
                "version": repo_stats.get('repo_name', 'unknown'),
                "human_time": human_hours,
                "human_cost": human_hours * 100
            }
        }
        
        # Update README with badge
        readme_path = project_dir / "README.md"
        if readme_path.exists():
            # Check if already has AI Cost Tracking section
            content = readme_path.read_text(encoding='utf-8')
            if "## AI Cost Tracking" not in content:
                success = update_readme_badge(project_dir, results)
                if success:
                    click.echo(click.style("  ✓ AI cost badge appended to README", fg='green'))
                else:
                    click.echo(click.style("  ⚠ Failed to update README badge", fg='yellow'))
            else:
                click.echo(click.style("  ✓ AI Cost Tracking section already exists", fg='green'))
        else:
            click.echo(click.style("  ⚠ README.md not found", fg='yellow'))
            
    except Exception as e:
        import traceback
        click.echo(click.style(f"  ⚠ Badge generation failed: {str(e)[:100]}", fg='yellow'))
    
    return True


def _ensure_costs_config(project_dir: Path) -> bool:
    """Ensure [tool.costs] section exists in pyproject.toml.
    
    Also adds goal and costs as dev dependencies.
    
    Returns True if config was added or already exists.
    """
    pyproject = project_dir / 'pyproject.toml'
    if not pyproject.exists():
        return False
    
    content = pyproject.read_text(encoding='utf-8')
    
    # Check/add dev dependencies for goal and costs
    dev_deps_updated = False
    
    # Look for common dev dependency groups
    dev_dep_patterns = [
        'dev-dependencies',
        'optional-dependencies',
    ]
    
    # Check if goal/costs/pfix already in dependencies
    has_goal = 'goal' in content.lower() and ('goal' in content.split('[project]')[1].split('[tool')[0] if '[project]' in content and '[tool' in content else True)
    has_costs = 'costs' in content.lower()
    has_pfix = 'pfix' in content.lower()
    
    if not has_goal or not has_costs or not has_pfix:
        # Try to add to [project.optional-dependencies] dev group
        if '[project.optional-dependencies]' in content and 'dev = [' in content.lower():
            # Find the dev group and add
            import re
            # Find dev = [ ... ] and add goal/costs if not present
            pattern = r'(dev\s*=\s*\[)([^\]]*)\]'
            def add_deps(match):
                existing = match.group(2)
                to_add = []
                if 'goal' not in existing.lower():
                    to_add.append('"goal>=2.1.0"')
                if 'costs' not in existing.lower():
                    to_add.append('"costs>=0.1.20"')
                if 'pfix' not in existing.lower():
                    to_add.append('"pfix>=0.1.60"')
                if to_add:
                    return f'{match.group(1)}{existing}{"," if existing.strip() else ""}{", ".join(to_add)}]'
                return match.group(0)
            
            new_content = re.sub(pattern, add_deps, content, flags=re.IGNORECASE | re.DOTALL)
            if new_content != content:
                content = new_content
                dev_deps_updated = True
        
        # Try hatch envs default
        elif '[tool.hatch.envs.default]' in content and 'dependencies = [' in content:
            pattern = r'(dependencies\s*=\s*\[)([^\]]*)\]'
            def add_deps_hatch(match):
                existing = match.group(2)
                to_add = []
                if 'goal' not in existing.lower():
                    to_add.append('"goal>=2.1.0"')
                if 'costs' not in existing.lower():
                    to_add.append('"costs>=0.1.20"')
                if 'pfix' not in existing.lower():
                    to_add.append('"pfix>=0.1.60"')
                if to_add:
                    return f'{match.group(1)}{existing}{"," if existing.strip() else ""}{", ".join(to_add)}]'
                return match.group(0)
            
            # Only match dependencies within hatch envs default
            hatch_section = content.split('[tool.hatch.envs.default]')[1].split('[')[0]
            if 'goal' not in hatch_section.lower() or 'costs' not in hatch_section.lower() or 'pfix' not in hatch_section.lower():
                new_content = re.sub(pattern, add_deps_hatch, content, flags=re.IGNORECASE | re.DOTALL)
                if new_content != content:
                    content = new_content
                    dev_deps_updated = True
    
    if dev_deps_updated:
        with open(pyproject, 'w', encoding='utf-8') as f:
            f.write(content)
        click.echo(click.style("  ✓ Added goal, costs and pfix to dev dependencies", fg='green'))
    
    # Check if already configured
    if '[tool.costs]' in content:
        return True
    
    # Add costs configuration
    costs_config = '''\n[tool.costs]
# AI Cost tracking configuration
badge = true
update_readme = true
readme_path = "README.md"
default_model = "openrouter/qwen/qwen3-coder-next"
analysis_mode = "byok"
full_history = true
max_commits = 500

# Cost thresholds for badge colors (USD)
badge_color_thresholds = { low = 1.0, medium = 5.0, high = 10.0, critical = 50.0 }
'''
    
    try:
        with open(pyproject, 'a', encoding='utf-8') as f:
            f.write(costs_config)
        click.echo(click.style("  ✓ Added [tool.costs] to pyproject.toml", fg='green'))
        return True
    except Exception as e:
        click.echo(click.style(f"  ⚠ Could not add costs config: {e}", fg='yellow'))
        return False


def _ensure_env_template(project_dir: Path) -> bool:
    """Ensure .env file exists with API key template.
    
    Returns True if .env was created or already exists.
    """
    env_file = project_dir / '.env'
    env_example = project_dir / '.env.example'
    
    # If .env already exists, don't modify it
    if env_file.exists():
        return True
    
    # Template content for .env
    env_template = '''# AI Cost Tracking Configuration
# Get your API key from: https://openrouter.ai/keys

# OpenRouter API Key (required for real cost calculation)
OPENROUTER_API_KEY=sk-or-v1-...

# Default AI model for cost analysis
LLM_MODEL=openrouter/qwen/qwen3-coder-next

# Optional: SaaS token for managed billing
# SAAS_TOKEN=your-saas-token
'''
    
    try:
        # Create .env from template
        env_file.write_text(env_template, encoding='utf-8')
        
        # Also create .env.example (for git)
        if not env_example.exists():
            env_example.write_text(env_template, encoding='utf-8')
        
        # Ensure .env is in .gitignore
        gitignore = project_dir / '.gitignore'
        if gitignore.exists():
            gitignore_content = gitignore.read_text(encoding='utf-8')
            if '.env' not in gitignore_content:
                with open(gitignore, 'a', encoding='utf-8') as f:
                    f.write('\n# Environment variables\n.env\n')
        else:
            gitignore.write_text('.env\n.env.local\n', encoding='utf-8')
        
        click.echo(click.style("  ✓ Created .env template (add your OPENROUTER_API_KEY)", fg='green'))
        return True
    except Exception as e:
        click.echo(click.style(f"  ⚠ Could not create .env: {e}", fg='yellow'))
        return False


def _ensure_pfix_installed(project_dir: Path, yes: bool = False) -> bool:
    """Ensure pfix package is installed for auto-fixing errors.
    
    Returns True if pfix is ready to use.
    """
    import subprocess
    
    python_bin = _find_python_bin(project_dir)
    
    # Check if pfix is already installed
    result = subprocess.run(
        [python_bin, '-c', 'import pfix; print(pfix.__version__)'],
        capture_output=True, text=True, cwd=str(project_dir)
    )
    
    if result.returncode != 0:
        # Install pfix
        click.echo(click.style(f"  Installing pfix package...", fg='cyan'))
        install_result = subprocess.run(
            [python_bin, '-m', 'pip', 'install', 'pfix>=0.1.60'],
            capture_output=True, text=True, cwd=str(project_dir)
        )
        if install_result.returncode != 0:
            click.echo(click.style(f"  ⚠ Could not install pfix package", fg='yellow'))
            return False
        click.echo(click.style("  ✓ Pfix package installed", fg='green'))
    else:
        click.echo(click.style(f"  ✓ Pfix package already installed ({result.stdout.strip()})", fg='green'))
    
    # Check/add pfix config to pyproject.toml
    _ensure_pfix_config(project_dir, yes=yes)
    
    return True


def _ensure_pfix_config(project_dir: Path, yes: bool = False) -> bool:
    """Ensure [tool.pfix] section exists in pyproject.toml.
    
    Returns True if config was added or already exists.
    """
    pyproject = project_dir / 'pyproject.toml'
    if not pyproject.exists():
        return False
    
    content = pyproject.read_text(encoding='utf-8')
    
    # Check if already configured
    if '[tool.pfix]' in content:
        return True
    
    # Add pfix configuration
    pfix_config = '''\n[tool.pfix]
# Self-healing Python configuration
model = "openrouter/qwen/qwen3-coder-next"
auto_apply = true
auto_install_deps = true
auto_restart = false
max_retries = 3
create_backups = false
git_auto_commit = false

[tool.pfix.runtime_todo]
enabled = true
todo_file = "TODO.md"
min_severity = "low"
deduplicate = true
'''
    
    try:
        with open(pyproject, 'a', encoding='utf-8') as f:
            f.write(pfix_config)
        click.echo(click.style("  ✓ Added [tool.pfix] to pyproject.toml", fg='green'))
        return True
    except Exception as e:
        click.echo(click.style(f"  ⚠ Could not add pfix config: {e}", fg='yellow'))
        return False
