"""Parser for pytest output."""

import re
from typing import List
from ..models import FailureRecord

# Patterns for extracting failures from pytest output
_FAILED_LINE = re.compile(r"^FAILED\s+(.+?)(?:\s+-\s+(.+))?$", re.MULTILINE)
_ERROR_LINE = re.compile(r"^ERROR\s+(.+?)(?:\s+-\s+(.+))?$", re.MULTILINE)
_SHORT_TB_ERROR = re.compile(r"^E\s+(.+Error.*)$", re.MULTILINE)
_SHORT_TB_EXCEPTION = re.compile(r"^E\s+(.*Exception.*)$", re.MULTILINE)
_EXCEPTION_TYPE = re.compile(
    r"\b(AttributeError|NameError|TypeError|ValueError|ImportError|"
    r"ModuleNotFoundError|TimeoutError|RuntimeError|AssertionError|"
    r"KeyError|IndexError|FileNotFoundError|PermissionError|"
    r"NotImplementedError|OSError|IOError|RecursionError|"
    r"StopIteration|GeneratorExit|SystemExit|Exception)\b"
)
_SECTION_HEADER = re.compile(r"^=+ (.+?) =+$", re.MULTILINE)
_FILE_LINE = re.compile(r"^(.+\.py):(\d+):")


class PytestParser:
    """Parse pytest output into FailureRecord objects."""

    def parse(self, text: str) -> List[FailureRecord]:
        """Parse pytest output text and return failure records."""
        records: List[FailureRecord] = []

        # Try to parse by sections (short/long traceback format)
        sections = self._split_into_failure_sections(text)
        if sections:
            for section_name, section_body in sections:
                record = self._parse_failure_section(section_name, section_body)
                if record:
                    records.append(record)
        else:
            # Fallback: extract from FAILED / ERROR lines
            records.extend(self._parse_failed_lines(text))
            records.extend(self._parse_error_lines(text))

        # Also grab error lines from E-prefixed lines if nothing found
        if not records:
            records.extend(self._parse_e_lines(text))

        return records

    def _split_into_failure_sections(self, text: str):
        """Split pytest output into per-failure sections."""
        sections = []
        # Look for ___ test_name ___ section headers
        header_pattern = re.compile(r"^_{3,}\s+(.+?)\s+_{3,}$", re.MULTILINE)
        headers = list(header_pattern.finditer(text))

        if not headers:
            return sections

        for i, match in enumerate(headers):
            name = match.group(1).strip()
            start = match.end()
            end = headers[i + 1].start() if i + 1 < len(headers) else len(text)
            body = text[start:end].strip()
            sections.append((name, body))

        return sections

    def _parse_failure_section(self, name: str, body: str) -> "FailureRecord | None":
        """Parse a single failure section."""
        # Determine error type
        error_type = "UnknownError"
        error_message = ""

        # Look for exception type in E-prefixed lines
        for line in body.splitlines():
            if line.startswith("E ") or line.startswith("E\t"):
                content = line[2:].strip()
                m = _EXCEPTION_TYPE.search(content)
                if m:
                    error_type = m.group(1)
                    error_message = content
                    break

        if not error_message:
            # Try to find any error-like line
            for line in body.splitlines():
                m = _EXCEPTION_TYPE.search(line)
                if m:
                    error_type = m.group(1)
                    error_message = line.strip()
                    break

        # Extract source file
        source_file = ""
        line_no = None
        for line in body.splitlines():
            fm = _FILE_LINE.match(line)
            if fm:
                source_file = fm.group(1)
                try:
                    line_no = int(fm.group(2))
                except ValueError:
                    pass
                break

        return FailureRecord(
            type=error_type,
            message=error_message or name,
            context=body[:500],
            source_file=source_file,
            line=line_no,
            raw=body,
        )

    def _parse_failed_lines(self, text: str) -> List[FailureRecord]:
        records = []
        for m in _FAILED_LINE.finditer(text):
            test_name = m.group(1).strip()
            description = (m.group(2) or "").strip()
            error_type = "TestFailure"
            em = _EXCEPTION_TYPE.search(description)
            if em:
                error_type = em.group(1)
            records.append(
                FailureRecord(
                    type=error_type,
                    message=description or test_name,
                    source_file=test_name.split("::")[0] if "::" in test_name else "",
                    raw=m.group(0),
                )
            )
        return records

    def _parse_error_lines(self, text: str) -> List[FailureRecord]:
        records = []
        for m in _ERROR_LINE.finditer(text):
            test_name = m.group(1).strip()
            description = (m.group(2) or "").strip()
            error_type = "CollectionError"
            em = _EXCEPTION_TYPE.search(description)
            if em:
                error_type = em.group(1)
            records.append(
                FailureRecord(
                    type=error_type,
                    message=description or test_name,
                    source_file=test_name.split("::")[0] if "::" in test_name else "",
                    raw=m.group(0),
                )
            )
        return records

    def _parse_e_lines(self, text: str) -> List[FailureRecord]:
        records = []
        for line in text.splitlines():
            if not (line.startswith("E ") or line.startswith("E\t")):
                continue
            content = line[2:].strip()
            m = _EXCEPTION_TYPE.search(content)
            if m:
                records.append(
                    FailureRecord(
                        type=m.group(1),
                        message=content,
                        raw=line,
                    )
                )
        return records
