"""Stdout-based parsing: test failures and performance/concurrency metrics."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

from ..benchmarking.models import ConcurrencyMetrics

if TYPE_CHECKING:
    from .models import TestResults

TEST_HEADER_RE = re.compile(r"_{3,}\s*(.*?)\s*_{3,}$")

_MIN_EQUALS_FOR_SECTION = 3


def parse_test_failures_from_stdout(
    stdout: str,
) -> dict[str, str]:
    """Extract individual pytest test failures by name."""
    lines = stdout.splitlines()
    start = _find_failures_start(lines)
    if start is None:
        return {}
    end = _find_failures_end(lines, start)
    return _collect_failures(lines[start:end])


def _find_failures_start(
    lines: list[str],
) -> int | None:
    """Find the index of the FAILURES header."""
    for i, line in enumerate(lines):
        if "= FAILURES =" in line:
            return i
    return None


def _find_failures_end(
    lines: list[str],
    start: int,
) -> int:
    """Find the end index of the failures section."""
    for j in range(start + 1, len(lines)):
        stripped = lines[j].strip()
        if "short test summary info" in stripped:
            return j
        if (
            stripped.startswith("=")
            and stripped.count("=") > _MIN_EQUALS_FOR_SECTION
        ):
            return j
    return len(lines)


def _collect_failures(
    failure_block: list[str],
) -> dict[str, str]:
    """Collect test failures from a block of lines."""
    failures: dict[str, str] = {}
    current_name: str | None = None
    current_lines: list[str] = []

    for line in failure_block:
        m = TEST_HEADER_RE.match(line.strip())
        if m:
            if current_name is not None:
                failures[current_name] = "".join(
                    current_lines,
                )
            current_name = m.group(1)
            current_lines = []
        elif current_name:
            current_lines.append(line + "\n")

    if current_name:
        failures[current_name] = "".join(current_lines)

    return failures


# -- Performance and concurrency metrics --

_perf_start_pattern = re.compile(
    r"!\$######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+)######\$!",
)
_perf_end_pattern = re.compile(
    r"!######([^:]*):([^:]*):([^:]*):([^:]*):([^:]+):([^:]+)######!",
)

_concurrency_pattern = re.compile(
    r"!@######CONC:"
    r"([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)"
    r":(\d+):(\d+):(\d+)######@!",
)


def calculate_function_throughput_from_test_results(
    test_results: TestResults,
    function_name: str,
) -> int:
    """Count completed function executions from performance stdout markers."""
    start_matches = _perf_start_pattern.findall(
        test_results.perf_stdout or "",
    )
    end_matches = _perf_end_pattern.findall(
        test_results.perf_stdout or "",
    )

    end_matches_truncated = [m[:5] for m in end_matches]
    end_matches_set = set(end_matches_truncated)

    count = 0
    expected_fn_idx = 2
    for start_match in start_matches:
        if (
            start_match in end_matches_set
            and len(start_match) > expected_fn_idx
            and start_match[expected_fn_idx] == function_name
        ):
            count += 1
    return count


def parse_concurrency_metrics(
    test_results: TestResults,
    function_name: str,
) -> ConcurrencyMetrics | None:
    """Parse concurrency benchmark results from test output."""
    if not test_results.perf_stdout:
        return None

    matches = _concurrency_pattern.findall(test_results.perf_stdout)
    if not matches:
        return None

    expected_groups = 8
    total_seq, total_conc, factor, count = 0, 0, 0, 0
    for match in matches:
        if len(match) >= expected_groups and match[3] == function_name:
            total_seq += int(match[5])
            total_conc += int(match[6])
            factor = int(match[7])
            count += 1

    if count == 0:
        return None

    avg_seq = total_seq / count
    avg_conc = total_conc / count
    ratio = avg_seq / avg_conc if avg_conc > 0 else 1.0

    return ConcurrencyMetrics(
        sequential_time_ns=int(avg_seq),
        concurrent_time_ns=int(avg_conc),
        concurrency_factor=factor,
        concurrency_ratio=ratio,
    )
