"""Validation profiler for performance analysis.

This module profiles validation runs to identify bottlenecks
and provide optimization recommendations.
"""

from __future__ import annotations

import json
import shutil
import subprocess
import tempfile
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional


@dataclass
class TimingBreakdown:
    """Timing breakdown for a phase of validation."""

    name: str
    duration_seconds: float
    sub_timings: List[TimingBreakdown] = field(default_factory=list)

    @property
    def percentage(self) -> float:
        """Placeholder - will be set by parent."""
        return 0.0

    def as_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "duration_seconds": self.duration_seconds,
            "sub_timings": [t.as_dict() for t in self.sub_timings],
        }


@dataclass
class ProfileResult:
    """Result of profiling a validation run."""

    total_duration: float
    docker_build: TimingBreakdown
    container_run: TimingBreakdown
    result_parse: TimingBreakdown
    manifest_info: Optional[Dict[str, Any]] = None
    recommendations: List[str] = field(default_factory=list)

    @property
    def timings(self) -> List[TimingBreakdown]:
        return [self.docker_build, self.container_run, self.result_parse]


def profile_docker_build(harness_dir: Path, tag: str) -> TimingBreakdown:
    """Profile Docker build phase.

    Args:
        harness_dir: Path to harness directory
        tag: Docker image tag

    Returns:
        TimingBreakdown for build phase
    """
    sub_timings = []

    # Check Dockerfile for pip install
    dockerfile_path = harness_dir / "Dockerfile"
    has_pip_install = False
    has_requirements = False

    if dockerfile_path.exists():
        content = dockerfile_path.read_text()
        has_pip_install = "pip install" in content
        has_requirements = "requirements.txt" in content

    start = time.time()

    # Run build with progress output
    proc = subprocess.run(
        ["docker", "build", "-t", tag, "--progress=plain", str(harness_dir)],
        capture_output=True,
        text=True,
    )

    total_build = time.time() - start

    # Parse build output for timing info
    pip_time = 0.0
    copy_time = 0.0
    other_time = total_build

    # Look for timing hints in output
    for line in proc.stdout.split("\n") + proc.stderr.split("\n"):
        line_lower = line.lower()
        if "pip install" in line_lower or "installing collected packages" in line_lower:
            # Rough estimate: pip takes ~60% of build for most projects
            pip_time = total_build * 0.6
            other_time -= pip_time
        if "copy" in line_lower and ("requirements" in line_lower or "src" in line_lower):
            copy_time = min(0.5, total_build * 0.05)
            other_time -= copy_time

    if has_pip_install or has_requirements:
        sub_timings.append(TimingBreakdown("pip install", pip_time))
    if copy_time > 0:
        sub_timings.append(TimingBreakdown("copy files", copy_time))
    if other_time > 0:
        sub_timings.append(TimingBreakdown("other", other_time))

    return TimingBreakdown("Docker build", total_build, sub_timings)


def profile_container_run(tag: str, output_dir: Path) -> TimingBreakdown:
    """Profile container run phase.

    Args:
        tag: Docker image tag
        output_dir: Directory for output

    Returns:
        TimingBreakdown for run phase
    """
    sub_timings = []

    output_dir.mkdir(parents=True, exist_ok=True)
    result_path = output_dir / "result.json"

    start = time.time()
    startup_start = start

    cmd = [
        "docker",
        "run",
        "--rm",
        "--network=none",
        "-v",
        f"{output_dir}:/output",
        tag,
    ]

    proc = subprocess.run(cmd, capture_output=True, text=True)

    total_run = time.time() - start

    # Estimate breakdown
    # Startup is typically ~30% for small containers
    startup_time = min(total_run * 0.3, 2.0)
    cleanup_time = min(total_run * 0.05, 0.5)
    test_time = total_run - startup_time - cleanup_time

    sub_timings.append(TimingBreakdown("startup", startup_time))
    sub_timings.append(TimingBreakdown("test exec", test_time))
    sub_timings.append(TimingBreakdown("cleanup", cleanup_time))

    return TimingBreakdown("Container run", total_run, sub_timings)


def profile_result_parse(output_dir: Path) -> TimingBreakdown:
    """Profile result parsing phase.

    Args:
        output_dir: Directory with results

    Returns:
        TimingBreakdown for parse phase
    """
    result_path = output_dir / "result.json"

    start = time.time()

    if result_path.exists():
        try:
            json.loads(result_path.read_text())
        except json.JSONDecodeError:
            pass

    duration = time.time() - start

    return TimingBreakdown("Result parse", duration)


def generate_recommendations(
    result: ProfileResult,
    manifest: Optional[Dict[str, Any]] = None,
) -> List[str]:
    """Generate optimization recommendations based on profile.

    Args:
        result: Profile result
        manifest: Optional manifest info

    Returns:
        List of recommendation strings
    """
    recommendations = []

    # Check Docker build time
    if result.docker_build.duration_seconds > 30:
        recommendations.append("Consider using a pre-built base image with dependencies")

    # Check pip install time
    for sub in result.docker_build.sub_timings:
        if sub.name == "pip install" and sub.duration_seconds > 10:
            recommendations.append("Cache pip packages in Docker layer or use a requirements lockfile")

    # Check container startup
    for sub in result.container_run.sub_timings:
        if sub.name == "startup" and sub.duration_seconds > 3:
            recommendations.append("Use a slim base image (python:3.x-slim) to reduce startup time")

    # Check test execution time
    for sub in result.container_run.sub_timings:
        if sub.name == "test exec" and sub.duration_seconds > 60:
            recommendations.append("Consider using transcript minimization to reduce test scope")

    # Check total time
    if result.total_duration > 60:
        recommendations.append("Total validation time is high - consider parallel validation runs")

    # Check for async issues
    if manifest:
        if manifest.get("async_detected"):
            recommendations.append("Async code detected - ensure deterministic await ordering")

        unsupported = manifest.get("unsupported_detected", [])
        if unsupported:
            recommendations.append(f"Unsupported dependencies detected: {', '.join(unsupported)}")

        # Check package count
        packages = manifest.get("env_fingerprint", {}).get("installed_packages", {})
        if len(packages) > 100:
            recommendations.append(f"Large dependency tree ({len(packages)} packages) - consider trimming unused deps")

    return recommendations


def profile_validation(
    harness_path: Path,
    runs: int = 1,
) -> ProfileResult:
    """Profile a validation run to identify bottlenecks.

    Args:
        harness_path: Path to harness tarball or directory
        runs: Number of validation runs to profile (default 1)

    Returns:
        ProfileResult with timing breakdown
    """
    manifest: Optional[Dict[str, Any]] = None

    with tempfile.TemporaryDirectory() as tmpdir:
        harness_dir = Path(tmpdir) / "harness"
        harness_dir.mkdir(parents=True, exist_ok=True)

        # Extract if tarball
        if harness_path.suffix in (".gz", ".tar", ".tgz"):
            shutil.unpack_archive(str(harness_path), harness_dir)
        else:
            # Copy directory contents
            shutil.copytree(harness_path, harness_dir, dirs_exist_ok=True)

        # Read manifest
        manifest_path = harness_dir / "manifest.json"
        if manifest_path.exists():
            manifest = json.loads(manifest_path.read_text())

        tag = f"autodebug-profile-{harness_path.stem}"
        output_dir = Path(tmpdir) / "output"

        total_start = time.time()

        # Profile each phase
        docker_build = profile_docker_build(harness_dir, tag)
        container_run = profile_container_run(tag, output_dir)
        result_parse = profile_result_parse(output_dir)

        total_duration = time.time() - total_start

    result = ProfileResult(
        total_duration=total_duration,
        docker_build=docker_build,
        container_run=container_run,
        result_parse=result_parse,
        manifest_info=manifest,
    )

    result.recommendations = generate_recommendations(result, manifest)

    return result


def format_profile_report(result: ProfileResult) -> str:
    """Format profile result as human-readable report.

    Args:
        result: ProfileResult to format

    Returns:
        Formatted report string
    """
    lines = [
        "",
        "Validation Profile Report",
        "=" * 50,
        "",
        f"Total time: {result.total_duration:.1f}s",
        "",
        "Breakdown:",
    ]

    for timing in result.timings:
        pct = (timing.duration_seconds / result.total_duration * 100) if result.total_duration > 0 else 0
        lines.append(f"  {timing.name}: {timing.duration_seconds:.1f}s ({pct:.0f}%)")

        for sub in timing.sub_timings:
            lines.append(f"    - {sub.name}: {sub.duration_seconds:.1f}s")

    if result.recommendations:
        lines.append("")
        lines.append("Recommendations:")
        for rec in result.recommendations:
            lines.append(f"  - {rec}")

    if result.manifest_info:
        lines.append("")
        lines.append("Manifest Info:")
        packages = result.manifest_info.get("env_fingerprint", {}).get("installed_packages", {})
        lines.append(f"  Packages: {len(packages)}")
        if result.manifest_info.get("async_detected"):
            lines.append("  Async: Yes")
        unsupported = result.manifest_info.get("unsupported_detected", [])
        if unsupported:
            lines.append(f"  Unsupported: {', '.join(unsupported)}")

    return "\n".join(lines)
