"""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, Tuple

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_python_name(project_dir: Path) -> Optional[str]:
    """Guess Python package name from pyproject.toml or setup.py."""
    import re as _re
    for filename, pattern, flags in [
        ('pyproject.toml', r'^name\s*=\s*["\']([^"\']+)["\']', _re.MULTILINE),
        ('setup.py',       r'name\s*=\s*["\']([^"\']+)["\']', 0),
    ]:
        path = project_dir / filename
        if not path.exists():
            continue
        try:
            m = _re.search(pattern, path.read_text(errors='ignore'), flags)
            if m:
                return m.group(1).replace('-', '_')
        except Exception:
            pass
    return None


def _guess_nodejs_name(project_dir: Path) -> Optional[str]:
    """Guess Node.js package name from package.json."""
    pkg = project_dir / 'package.json'
    if not pkg.exists():
        return None
    try:
        data = json.loads(pkg.read_text(errors='ignore'))
        name = data.get('name', '')
        return name.split('/')[-1] if '/' in name else name
    except Exception:
        return None


def _guess_rust_name(project_dir: Path) -> Optional[str]:
    """Guess Rust crate name from Cargo.toml."""
    import re as _re
    cargo = project_dir / 'Cargo.toml'
    if not cargo.exists():
        return None
    try:
        m = _re.search(r'^name\s*=\s*"([^"]+)"', cargo.read_text(errors='ignore'), _re.MULTILINE)
        return m.group(1) if m else None
    except Exception:
        return None


def _guess_go_name(project_dir: Path) -> Optional[str]:
    """Guess Go module name from go.mod."""
    gomod = project_dir / 'go.mod'
    if not gomod.exists():
        return None
    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
    return None


_PACKAGE_NAME_DETECTORS = {
    'python': _guess_python_name,
    'nodejs': _guess_nodejs_name,
    'rust':   _guess_rust_name,
    'go':     _guess_go_name,
}


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()
    detector = _PACKAGE_NAME_DETECTORS.get(project_type)
    if detector:
        name = detector(project_dir)
        if name:
            return name
    # 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 _read_openrouter_api_key(env_file: Path) -> str:
    """Extract OPENROUTER_API_KEY from a .env file."""
    try:
        content = env_file.read_text(encoding='utf-8')
    except Exception:
        return ''

    for line in content.splitlines():
        stripped = line.strip()
        if not stripped or stripped.startswith('#'):
            continue
        if stripped.startswith('export '):
            stripped = stripped[len('export '):].strip()

        key_name, sep, value = stripped.partition('=')
        if not sep or key_name.strip() != 'OPENROUTER_API_KEY':
            continue

        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
            value = value[1:-1].strip()
        return value

    return ''


def _find_openrouter_api_key(project_dir: Path) -> Tuple[Optional[Path], str]:
    """Find an OpenRouter API key in the environment or any ancestor .env file."""
    env_key = os.environ.get('OPENROUTER_API_KEY', '').strip()
    if env_key:
        return None, env_key

    search_dir = project_dir.resolve()
    while True:
        candidate = search_dir / '.env'
        if candidate.exists():
            api_key = _read_openrouter_api_key(candidate)
            if api_key:
                return candidate, api_key

        if search_dir == search_dir.parent:
            break
        search_dir = search_dir.parent

    return None, ''


def _find_git_root(project_dir: Path) -> Optional[Path]:
    """Find the git repository root for a project directory."""
    current = project_dir.resolve()
    while current != current.parent:
        if (current / '.git').exists():
            return current
        current = current.parent
    return None


def _ensure_python_test_dependency(project_dir: Path, python_bin: str, test_dep: Optional[str]) -> bool:
    """Ensure the Python test runner dependency is available in the project venv."""
    if not test_dep:
        return True

    check_result = subprocess.run(
        [python_bin, '-c', f'import {test_dep}; print({test_dep}.__version__)'],
        capture_output=True, text=True, cwd=str(project_dir)
    )
    if check_result.returncode == 0:
        version = check_result.stdout.strip()
        if version:
            click.echo(click.style(f"  ✓ {test_dep} already installed ({version})", fg='green'))
        else:
            click.echo(click.style(f"  ✓ {test_dep} already installed", fg='green'))
        return True

    click.echo(click.style(f"  Installing test dependency: {test_dep}", fg='cyan'))
    install_result = subprocess.run(
        [python_bin, '-m', 'pip', 'install', test_dep],
        capture_output=True, text=True, cwd=str(project_dir)
    )
    if install_result.returncode != 0:
        click.echo(click.style(f"  ⚠ Could not install test dependency: {test_dep}", fg='yellow'))
        return False

    click.echo(click.style(f"  ✓ Test dependency installed ({test_dep})", fg='green'))
    return True


def _ensure_python_env(project_dir: Path, cfg: dict, yes: bool) -> bool:
    """Set up Python project environment: venv, pip, costs, deps, test deps."""
    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 (first matching only)
    _install_python_deps(project_dir, cfg, python_bin)

    test_dep = cfg.get('test_dep')
    return _ensure_python_test_dependency(project_dir, python_bin, test_dep)


def _install_python_deps(project_dir: Path, cfg: dict, python_bin: str) -> None:
    """Run the first matching dependency install command for a Python project."""
    for dep_cfg in cfg['dep_install_commands']:
        if not _match_marker(project_dir, dep_cfg['condition']):
            continue
        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:
            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'))
        else:
            click.echo(click.style("  ✓ Dependencies installed", fg='green'))
        break  # Only run the first matching dep install


def _ensure_generic_env(project_dir: Path, project_type: str, cfg: dict, yes: bool) -> bool:
    """Set up a non-Python project environment (Node, Rust, Go, etc.)."""
    env_dir = cfg.get('env_dir')
    if env_dir and (project_dir / env_dir).exists():
        return True  # Already set up

    needs_install = env_dir and not (project_dir / env_dir).exists()
    if not needs_install:
        for dep_cfg in cfg['dep_install_commands']:
            if _match_marker(project_dir, dep_cfg['condition']):
                needs_install = True
                break

    if not needs_install:
        return True

    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 not _match_marker(project_dir, dep_cfg['condition']):
            continue
        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


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()

    if project_type == 'python':
        return _ensure_python_env(project_dir, cfg, yes)
    return _ensure_generic_env(project_dir, project_type, cfg, yes)


# =============================================================================
# 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


# Test file name templates per project type.
# Value is a format string receiving {package_name}; use 'test_dir' or 'project_dir' as base.
_TEST_FILE_NAMES = {
    'python': ('test_dir', 'test_{package_name}.py'),
    'nodejs': ('test_dir', '{package_name}.test.js'),
    'rust':   ('test_dir', 'placeholder_test.rs'),
    'go':     ('project_dir', 'placeholder_test.go'),
    'ruby':   ('test_dir', '{package_name}_spec.rb'),
    'php':    ('test_dir', 'PlaceholderTest.php'),
    'dotnet': ('test_dir', 'PlaceholderTest.cs'),
    'java':   ('test_dir', 'PlaceholderTest.java'),
}


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

    template = _TEST_FILE_NAMES.get(project_type)
    if not template:
        return None

    package_name = guess_package_name(project_dir, project_type)
    test_dir_name = cfg['test_dirs'][0]
    test_dir = project_dir / test_dir_name

    base_key, name_fmt = template
    base = test_dir if base_key == 'test_dir' else project_dir
    test_file = base / name_fmt.format(package_name=package_name)

    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 _install_costs_package(project_dir: Path, python_bin: str) -> bool:
    """Check if costs is installed; install it if not. Returns True on success."""
    import subprocess
    result = subprocess.run(
        [python_bin, '-c', 'import costs; print(costs.__version__)'],
        capture_output=True, text=True, cwd=str(project_dir)
    )
    if result.returncode == 0:
        click.echo(click.style(f"  ✓ Costs package already installed ({result.stdout.strip()})", fg='green'))
        return True

    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'))
    return True


def _load_costs_api():
    """Import costs API functions, falling back to older API versions."""
    try:
        from costs import calculate_human_time, update_readme_badge
    except ImportError:
        from costs.reports import update_readme_badge
        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():
                    total_hours += min(len(author_commits) * 0.5, 8.0)
            return total_hours

    return calculate_human_time, update_readme_badge


def _calculate_ai_costs(repo_root: Path):
    """Parse commits and calculate AI-related costs. Returns (total_cost, total_commits, all_commits_data)."""
    from costs.git_parser import parse_commits, get_commit_diff
    from costs.calculator import ai_cost

    all_commits_data = parse_commits(
        str(repo_root), max_count=500, ai_only=False, full_history=True
    )

    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)
    ]

    total_cost = 0.0
    total_commits = len(ai_commits)

    for commit_obj, _msg in ai_commits[:50]:
        try:
            diff = get_commit_diff(str(repo_root), commit_obj.hexsha)
            if diff:
                total_cost += ai_cost(diff, model='openrouter/qwen/qwen3-coder-next').get('cost', 0.0)
        except Exception:
            total_cost += 0.15

    if total_cost == 0 and all_commits_data:
        total_cost = len(all_commits_data) * 0.15
        total_commits = len(all_commits_data)

    return total_cost, total_commits, all_commits_data


def _read_model_from_pyproject(project_dir: Path) -> str:
    """Read default_model from pyproject.toml, or return a default."""
    import re as _re
    pyproject = project_dir / 'pyproject.toml'
    if pyproject.exists():
        content = pyproject.read_text()
        match = _re.search(r'default_model\s*=\s*"([^"]+)"', content)
        if match:
            return match.group(1)
    return "openrouter/qwen/qwen3-coder-next"


def _generate_costs_badge(project_dir: Path) -> None:
    """Generate an AI cost badge in the project README."""
    click.echo(click.style(f"  Generating AI cost badge...", fg='cyan'))
    try:
        repo_root = _find_git_root(project_dir) or project_dir
        calculate_human_time, update_readme_badge = _load_costs_api()
        from costs.git_parser import get_repo_stats

        repo_stats = get_repo_stats(str(repo_root))
        total_cost, total_commits, all_commits_data = _calculate_ai_costs(repo_root)
        model = _read_model_from_pyproject(project_dir)

        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)

        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
            }
        }

        readme_path = repo_root / "README.md"
        if readme_path.exists():
            success = update_readme_badge(repo_root, results)
            if success:
                click.echo(click.style("  ✓ AI cost badge updated in README", fg='green'))
            else:
                click.echo(click.style("  ⚠ Failed to update README badge", fg='yellow'))
        else:
            click.echo(click.style("  ⚠ README.md not found", fg='yellow'))
    except Exception as e:
        click.echo(click.style(f"  ⚠ Badge generation failed: {str(e)[:100]}", fg='yellow'))


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.
    """
    if not _install_costs_package(project_dir, python_bin):
        return False
    
    _ensure_costs_config(project_dir)
    _ensure_env_template(project_dir)
    _generate_costs_badge(project_dir)
    
    return True


_REQUIRED_DEV_DEPS = [
    ('goal',  '"goal>=2.1.0"'),
    ('costs', '"costs>=0.1.20"'),
    ('pfix',  '"pfix>=0.1.60"'),
]


def _add_deps_to_section(match, required_deps=_REQUIRED_DEV_DEPS):
    """Regex replacement callback: append missing deps to a TOML list."""
    existing = match.group(2)
    to_add = [spec for name, spec in required_deps if name not in existing.lower()]
    if not to_add:
        return match.group(0)

    existing_stripped = existing.rstrip()
    indent = '    '
    for line in existing.split('\n'):
        stripped = line.lstrip()
        if stripped.startswith('"'):
            indent = line[:len(line) - len(stripped)] or indent
            break
    new_entries = '\n'.join(f'{indent}{dep},' for dep in to_add)
    if existing_stripped and not existing_stripped.endswith(','):
        existing_stripped += ','
    return f'{match.group(1)}{existing_stripped}\n{new_entries}\n]'


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.
    """
    import re
    pyproject = project_dir / 'pyproject.toml'
    if not pyproject.exists():
        return False
    
    content = pyproject.read_text(encoding='utf-8')
    
    # Check if any required dep is missing
    all_present = all(name in content.lower() for name, _ in _REQUIRED_DEV_DEPS)
    dev_deps_updated = False

    if not all_present:
        dep_pattern = r'((?:dev|dependencies)\s*=\s*\[)([^\]]*)\]'
        if '[project.optional-dependencies]' in content and 'dev = [' in content.lower():
            new_content = re.sub(dep_pattern, _add_deps_to_section, content,
                                 flags=re.IGNORECASE | re.DOTALL)
            if new_content != content:
                content = new_content
                dev_deps_updated = True
        elif '[tool.hatch.envs.default]' in content and 'dependencies = [' in content:
            hatch_section = content.split('[tool.hatch.envs.default]')[1].split('[')[0]
            if not all(name in hatch_section.lower() for name, _ in _REQUIRED_DEV_DEPS):
                new_content = re.sub(dep_pattern, _add_deps_to_section, 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'))
    
    if '[tool.costs]' in content:
        return True
    
    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_env(project_dir: Path) -> bool:
    """Ensure .env file exists with pfix API key template.
    
    Returns True if .env exists or was created.
    """
    env_file = project_dir / '.env'
    env_example = project_dir / '.env.example'
    
    # Template content for pfix .env
    env_template = '''# pfix Configuration
# Self-healing Python — auto-fixes runtime errors via LLM

# Required: OpenRouter API key (https://openrouter.ai/keys)
OPENROUTER_API_KEY=

# Model (default: openrouter/qwen/qwen3-coder-next)
LLM_MODEL=openrouter/qwen/qwen3-coder-next

# Behavior
PFIX_AUTO_APPLY=true         # true = apply fixes without asking
PFIX_AUTO_INSTALL_DEPS=true   # true = auto pip/uv install
PFIX_AUTO_RESTART=false        # true = os.execv restart after fix
PFIX_MAX_RETRIES=3
PFIX_DRY_RUN=false
PFIX_ENABLED=true

# Git integration
PFIX_GIT_COMMIT=false         # true = auto-commit fixes
PFIX_GIT_PREFIX=pfix:         # commit message prefix

# Backup
PFIX_CREATE_BACKUPS=false     # false = disable .pfix_backups/ directory
'''

    _, existing_api_key = _find_openrouter_api_key(project_dir)
    if existing_api_key:
        return True
    
    created = False
    
    try:
        # Create .env from template if not exists
        if not env_file.exists():
            env_file.write_text(env_template, encoding='utf-8')
            click.echo(click.style("  ✓ Created .env template (add your OPENROUTER_API_KEY)", fg='green'))
            created = True
        
        # 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')
        
        return True
    except Exception as e:
        click.echo(click.style(f"  ⚠ Could not create pfix .env: {e}", fg='yellow'))
        return False


def _validate_pfix_env(project_dir: Path) -> bool:
    """Validate that OPENROUTER_API_KEY is configured in .env.
    
    Shows error message if key is missing or empty.
    Returns True if key is present, False otherwise.
    """
    env_file, api_key = _find_openrouter_api_key(project_dir)
    if not env_file:
        env_file = project_dir / '.env'
    
    if not api_key:
        click.echo(click.style("\n  ⚠️ OPENROUTER_API_KEY not configured!", fg='red', bold=True))
        click.echo(click.style("  pfix requires an OpenRouter API key to fix errors.", fg='yellow'))
        click.echo(click.style("\n  To fix this:", fg='cyan'))
        click.echo(click.style("  1. Get your API key from: https://openrouter.ai/keys", fg='yellow'))
        click.echo(click.style(f"  2. Add it to: {env_file}", fg='yellow'))
        click.echo(click.style("  3. Restart goal -a\n", fg='yellow'))
        return False
    
    # Check if key looks valid (starts with sk-or-v1-)
    if not api_key.startswith('sk-or-v1-'):
        click.echo(click.style("\n  ⚠️ OPENROUTER_API_KEY format appears invalid!", fg='red', bold=True))
        click.echo(click.style("  Expected format: sk-or-v1-...", fg='yellow'))
        click.echo(click.style("\n  Please verify your API key at: https://openrouter.ai/keys", fg='cyan'))
        return False
    
    click.echo(click.style("  ✓ OPENROUTER_API_KEY configured", fg='green'))
    return True


def _ensure_pfix_installed(project_dir: Path, yes: bool = False) -> bool:
    """Ensure pfix package is installed for auto-fixing errors.
    
    Installs from local development path /home/tom/github/semcod/pfix
    in editable mode for development workflow integration.
    
    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)
    )
    
    PFIX_LOCAL_PATH = "/home/tom/github/semcod/pfix"
    
    if result.returncode != 0:
        # Install pfix from local path in editable mode
        click.echo(click.style(f"  Installing pfix from local path...", fg='cyan'))
        install_result = subprocess.run(
            [python_bin, '-m', 'pip', 'install', '-e', PFIX_LOCAL_PATH],
            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 (editable)", 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)
    
    # Create .env template for API key if not exists
    env_ok = _ensure_pfix_env(project_dir)
    
    # Validate API key is configured
    if env_ok:
        _validate_pfix_env(project_dir)
    
    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
