"""Data models for optimization requests, candidates, and PR comments."""

from __future__ import annotations

import attrs


@attrs.frozen
class OptimizationRequest:
    """
    Input for the AI optimization service.
    """

    source_code: str
    language: str
    language_version: str
    context_code: str = ""
    is_async: bool = False
    is_numerical_code: bool | None = None
    codeflash_version: str = ""
    baseline_runtime_ns: int | None = None
    loop_count: int | None = None
    line_profiler_results: str | None = None
    test_input_examples: str | None = None


@attrs.frozen
class Candidate:
    """
    A single optimization candidate returned by the AI service.
    """

    code: str
    explanation: str
    candidate_id: str = ""
    source: str = ""
    parent_id: str = ""
    code_markdown: str = ""


@attrs.frozen
class OptimizationReviewResult:
    """
    Result from the optimization review API.
    """

    review: str
    explanation: str


@attrs.frozen
class FileDiffContent:
    """Old/new file content for a PR diff."""

    old_content: str
    new_content: str


_NS_PER_US = 1_000
_NS_PER_MS = 1_000_000
_NS_PER_S = 1_000_000_000
_NS_PER_MIN = 60_000_000_000
_NS_PER_HOUR = 3_600_000_000_000
_NS_PER_DAY = 86_400_000_000_000


def humanize_runtime(time_in_ns: int) -> str:
    """Format *time_in_ns* into a human-friendly string."""
    runtime_human: str = str(time_in_ns)
    units = "nanoseconds"
    if 1 <= time_in_ns < 2:
        units = "nanosecond"

    if time_in_ns >= _NS_PER_US:
        time_micro = float(time_in_ns) / _NS_PER_US
        if time_micro < _NS_PER_US:
            runtime_human = f"{time_micro:.3g}"
            units = "microseconds" if time_micro >= 2 else "microsecond"
        elif time_micro < _NS_PER_MS:
            time_milli = time_micro / _NS_PER_US
            runtime_human = f"{time_milli:.3g}"
            units = "milliseconds" if time_milli >= 2 else "millisecond"
        elif time_micro < _NS_PER_MIN / _NS_PER_US:
            time_sec = time_micro / _NS_PER_MS
            runtime_human = f"{time_sec:.3g}"
            units = "seconds" if time_sec >= 2 else "second"
        elif time_micro < _NS_PER_HOUR / _NS_PER_US:
            time_min = time_micro / (_NS_PER_MIN / _NS_PER_US)
            runtime_human = f"{time_min:.3g}"
            units = "minutes" if time_min >= 2 else "minute"
        elif time_micro < _NS_PER_DAY / _NS_PER_US:
            time_hour = time_micro / (_NS_PER_HOUR / _NS_PER_US)
            runtime_human = f"{time_hour:.3g}"
            units = "hours" if time_hour >= 2 else "hour"
        else:
            time_day = time_micro / (_NS_PER_DAY / _NS_PER_US)
            runtime_human = f"{time_day:.3g}"
            units = "days" if time_day >= 2 else "day"

    parts = str(runtime_human).split(".")
    if len(parts[0]) == 1:
        if parts[0] == "1" and len(parts) > 1:
            units = units + "s"
        if len(parts) == 1:
            runtime_human = f"{parts[0]}.00"
        elif len(parts[1]) >= 2:
            runtime_human = f"{parts[0]}.{parts[1][:2]}"
        else:
            runtime_human = f"{parts[0]}.{parts[1]}{'0' * (2 - len(parts[1]))}"
    elif len(parts[0]) == 2:
        runtime_human = (
            f"{parts[0]}.{parts[1][0]}" if len(parts) > 1 else f"{parts[0]}.0"
        )
    else:
        runtime_human = parts[0]

    return f"{runtime_human} {units}"


@attrs.frozen
class BenchmarkDetail:
    """Detail for a single benchmark in a PR comment."""

    benchmark_name: str
    test_function: str
    original_timing: str
    expected_new_timing: str
    speedup_percent: float

    def to_string(self) -> str:
        """Return a human-readable multi-line summary of this benchmark."""
        n = f"{self.benchmark_name}::{self.test_function}"
        return (
            f"Original timing for {n}: "
            f"{self.original_timing}\n"
            f"Expected new timing for {n}: "
            f"{self.expected_new_timing}\n"
            f"Benchmark speedup for {n}: "
            f"{self.speedup_percent:.2f}%\n"
        )

    def to_dict(self) -> dict[str, object]:
        """Serialize this benchmark detail to a plain dictionary."""
        return {
            "benchmark_name": self.benchmark_name,
            "test_function": self.test_function,
            "original_timing": self.original_timing,
            "expected_new_timing": self.expected_new_timing,
            "speedup_percent": self.speedup_percent,
        }


@attrs.frozen
class PrComment:
    """Data for a GitHub PR comment about an optimization.

    All fields are primitives so this is language-agnostic.
    Language-specific packages provide factory functions that
    build a *PrComment* from their own test-result types.
    """

    optimization_explanation: str
    best_runtime: int
    original_runtime: int
    function_name: str
    relative_file_path: str
    speedup_x: str
    speedup_pct: str
    loop_count: int
    report_table: dict[str, dict[str, int]]
    benchmark_details: tuple[BenchmarkDetail, ...] | None = None
    original_async_throughput: int | None = None
    best_async_throughput: int | None = None

    def to_json(self) -> dict[str, object]:
        """Serialize this PR comment to a JSON-compatible dictionary."""
        result: dict[str, object] = {
            "optimization_explanation": self.optimization_explanation,
            "best_runtime": humanize_runtime(self.best_runtime),
            "original_runtime": humanize_runtime(
                self.original_runtime,
            ),
            "function_name": self.function_name,
            "file_path": self.relative_file_path,
            "speedup_x": self.speedup_x,
            "speedup_pct": self.speedup_pct,
            "loop_count": self.loop_count,
            "report_table": self.report_table,
            "benchmark_details": (self.benchmark_details or None),
        }

        if (
            self.original_async_throughput is not None
            and self.best_async_throughput is not None
        ):
            result["original_async_throughput"] = (
                self.original_async_throughput
            )
            result["best_async_throughput"] = self.best_async_throughput

        return result
