from __future__ import annotations

import re

from ai_orchestrator.schemas.workflow_models import StructuredError

PYTEST_FAILURE_RE = re.compile(
    r"FAILED\s+(?P<file>[^\s:]+)::(?P<test_name>[^\s]+)\s*-\s*(?P<error_type>[A-Za-z_][\w.]*)"
)
TRACEBACK_RE = re.compile(r'File "(?P<file>[^"]+)", line (?P<line>\d+)')
PATCH_FAILURE_RE = re.compile(r"error: patch failed: (?P<file>.+?):(?P<line>\d+)")
ASSERTION_RE = re.compile(r"(?P<error_type>AssertionError|TypeError|ValueError|ImportError|SyntaxError):?\s*(?P<message>.*)")


def extract_structured_errors(stdout: str, stderr: str) -> list[StructuredError]:
    text = "\n".join(part for part in [stdout, stderr] if part)
    errors: list[StructuredError] = []

    for match in PYTEST_FAILURE_RE.finditer(text):
        errors.append(
            StructuredError(
                error_type=match.group("error_type"),
                file=match.group("file"),
                test_name=match.group("test_name"),
                message=match.group(0),
            )
        )

    for match in PATCH_FAILURE_RE.finditer(text):
        errors.append(
            StructuredError(
                error_type="PatchApplyError",
                file=match.group("file"),
                line=int(match.group("line")),
                message=match.group(0),
            )
        )

    traceback_matches = list(TRACEBACK_RE.finditer(text))
    assertion_match = ASSERTION_RE.search(text)
    if traceback_matches:
        last_trace = traceback_matches[-1]
        traceback_files: list[str] = []
        for trace in traceback_matches:
            file_path = trace.group("file")
            if file_path not in traceback_files:
                traceback_files.append(file_path)
        errors.append(
            StructuredError(
                error_type=assertion_match.group("error_type") if assertion_match else "RuntimeError",
                file=last_trace.group("file"),
                line=int(last_trace.group("line")),
                message=assertion_match.group("message").strip() if assertion_match else last_trace.group(0),
                metadata={"traceback_files": traceback_files},
            )
        )
    elif assertion_match:
        errors.append(
            StructuredError(
                error_type=assertion_match.group("error_type"),
                message=assertion_match.group("message").strip() or assertion_match.group(0),
            )
        )

    deduped: list[StructuredError] = []
    seen: set[tuple[str, str | None, int | None, str | None]] = set()
    for error in errors:
        key = (error.error_type, error.file, error.line, error.test_name)
        if key in seen:
            continue
        seen.add(key)
        deduped.append(error)
    return deduped
