"""Flaky test pattern definitions.

This module defines language-agnostic patterns for detecting
common causes of test flakiness. Each pattern includes:
- Detection rules (regex, AST patterns)
- Severity and confidence scores
- Fix strategies and templates
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Dict, List, Optional


class PatternCategory(Enum):
    """Categories of flaky test patterns based on research."""

    ASYNC_WAIT = auto()      # 45% of flaky tests - hardcoded delays, missing waits
    CONCURRENCY = auto()     # 20% - race conditions, thread safety
    ORDER_DEPENDENCY = auto() # 12% - shared state, test isolation
    RESOURCE_LEAK = auto()   # 8% - unclosed resources
    TIME_DEPENDENT = auto()  # 5% - wall clock, timezone issues
    RANDOMNESS = auto()      # 5% - unseeded random, non-determinism
    NETWORK = auto()         # 3% - flaky external calls
    FLOATING_POINT = auto()  # 1% - precision issues
    PLATFORM = auto()        # 1% - OS-specific behavior
    ENVIRONMENT = auto()     # CI vs local, Docker, locale-dependent


# Bidirectional mapping between PatternCategory and FlakinessCategory (api.ai.models)
# FlakinessCategory values are the string enum values from api.ai.models.FlakinessCategory
PATTERN_TO_FLAKINESS: Dict[PatternCategory, str] = {
    PatternCategory.ASYNC_WAIT: "timing",
    PatternCategory.CONCURRENCY: "concurrency",
    PatternCategory.ORDER_DEPENDENCY: "ordering",
    PatternCategory.RESOURCE_LEAK: "resource",
    PatternCategory.TIME_DEPENDENT: "timing",
    PatternCategory.RANDOMNESS: "randomness",
    PatternCategory.NETWORK: "network",
    PatternCategory.FLOATING_POINT: "floating_point",
    PatternCategory.PLATFORM: "platform",
    PatternCategory.ENVIRONMENT: "environment",
}

FLAKINESS_TO_PATTERNS: Dict[str, List[PatternCategory]] = {}
for _pc, _fc in PATTERN_TO_FLAKINESS.items():
    FLAKINESS_TO_PATTERNS.setdefault(_fc, []).append(_pc)


class FixStrategy(Enum):
    """Strategies for fixing flaky patterns."""

    REPLACE = auto()         # Replace the problematic code
    WRAP = auto()            # Wrap with retry/wait logic
    ADD_CLEANUP = auto()     # Add teardown/cleanup code
    ADD_SETUP = auto()       # Add setup/initialization
    ADD_MOCK = auto()        # Mock external dependency
    ADD_SEED = auto()        # Add deterministic seed
    ADD_TOLERANCE = auto()   # Add tolerance for comparisons
    REORDER = auto()         # Reorder operations
    TEMPLATE = auto()        # Apply fix template
    LLM = auto()             # Require LLM for complex fix


@dataclass
class FixTemplate:
    """Template for fixing a pattern.

    Confidence Calibration Methodology
    -----------------------------------
    The ``confidence`` field represents the estimated probability that applying
    this template will resolve the flaky behaviour *without introducing a new
    regression*.  Values were initially set via expert judgement using the
    following rubric and should be refined with empirical data as it becomes
    available (see ``recalibrate_confidence`` below).

    Rubric for initial estimates:
        0.90 - 0.95  Mechanical / deterministic fix (e.g. add ``pytest.approx``,
                     use ``tmp_path``, add ``random.seed``).  Virtually no
                     ambiguity in what the correct fix looks like.
        0.80 - 0.89  High-confidence structural fix (e.g. add ``await``, mock
                     time, increase timeout with CI multiplier).  Correct in
                     most cases but context may require adaptation.
        0.70 - 0.79  Context-dependent fix (e.g. replace ``sleep`` with
                     condition-based wait).  Requires identifying the right
                     condition, so template alone is not always sufficient.
        0.60 - 0.69  Best-effort / heuristic fix (e.g. add a lock around a
                     race condition, guard container-specific code).  Often
                     needs manual review or LLM assistance.

    Once sufficient historical fix-success data exists (recommended: >= 30
    applications per template), call ``recalibrate_confidence`` to replace
    the hand-picked estimate with the empirical success rate.
    """

    strategy: FixStrategy
    description: str
    # Language -> template mapping
    # Templates use {placeholder} for substitution
    templates: Dict[str, str] = field(default_factory=dict)
    # Context needed for the fix
    context_needed: List[str] = field(default_factory=list)
    # Confidence that this fix will work (0-1).
    # See class docstring for calibration methodology.
    confidence: float = 0.8

    def recalibrate_confidence(
        self,
        successes: int,
        total_applications: int,
        *,
        min_samples: int = 30,
        prior_weight: float = 0.3,
    ) -> float:
        """Update confidence using empirical success data.

        Uses a Bayesian-inspired blend: the prior (hand-picked estimate) is
        blended with the observed success rate, with the prior's influence
        shrinking as more data arrives.

        WARNING: This mutates the module-level singleton pattern instances.
        In multi-process deploys this is process-local and non-persistent.
        Future: store calibrated values in DB, keyed by (org_id, template_id).

        Args:
            successes: Number of times applying this template resolved the flake.
            total_applications: Total number of times this template was applied.
            min_samples: Minimum applications before updating; returns current
                confidence unchanged if ``total_applications < min_samples``.
            prior_weight: Weight given to the original estimate when
                ``total_applications == min_samples``.  Decays as
                ``prior_weight * min_samples / total_applications``.

        Returns:
            The updated confidence value (also stored on ``self.confidence``).
        """
        if total_applications < min_samples:
            return self.confidence
        successes = max(0, min(successes, total_applications))
        empirical_rate = successes / total_applications
        effective_prior_weight = prior_weight * min_samples / total_applications
        self.confidence = max(0.0, min(1.0,
            effective_prior_weight * self.confidence
            + (1 - effective_prior_weight) * empirical_rate
        ))
        return self.confidence


@dataclass
class FlakyPattern:
    """Definition of a flaky test pattern."""

    id: str
    name: str
    category: PatternCategory
    description: str
    severity: int  # 1-5, higher = more likely to cause flakiness

    # Detection rules - language-agnostic patterns
    # These are converted to language-specific patterns by adapters
    code_patterns: List[str]  # Regex patterns to match
    ast_patterns: List[Dict[str, Any]] = field(default_factory=list)  # AST node patterns

    # Anti-patterns - if these exist, the pattern is likely already handled
    anti_patterns: List[str] = field(default_factory=list)

    # Fix information
    fix_templates: List[FixTemplate] = field(default_factory=list)
    fix_explanation: str = ""

    # Research reference
    research_citation: Optional[str] = None

    # Per-pattern base confidence (E4/F-06): calibrated based on pattern
    # specificity rather than a flat per-language value.  Language adapters
    # use this when available; if None they fall back to their language default.
    #   0.90-0.95  Highly specific (e.g. time.sleep, random without seed)
    #   0.80-0.89  Specific structural match
    #   0.70-0.79  Moderate -- context-dependent
    #   0.55-0.69  Broad / high false-positive rate
    base_confidence: Optional[float] = None

    def __hash__(self):
        return hash(self.id)


@dataclass
class PatternMatch:
    """A match of a pattern in code."""

    pattern: FlakyPattern
    file_path: str
    line_number: int
    column: int
    matched_code: str
    context_before: List[str]  # Lines before the match
    context_after: List[str]   # Lines after the match
    confidence: float          # How confident we are this is actually flaky
    language: str
    additional_info: Dict[str, Any] = field(default_factory=dict)

    @property
    def location(self) -> str:
        return f"{self.file_path}:{self.line_number}:{self.column}"


# =============================================================================
# Built-in Patterns
# =============================================================================

ASYNC_WAIT_PATTERNS = [
    FlakyPattern(
        id="async-wait-sleep",
        name="Hardcoded Sleep",
        category=PatternCategory.ASYNC_WAIT,
        description="Using time.sleep() or equivalent instead of proper async waiting",
        severity=5,
        base_confidence=0.90,
        code_patterns=[
            r"time\.sleep\s*\(",           # Python
            r"Thread\.sleep\s*\(",          # Java
            r"await\s+sleep\s*\(",          # JS/TS
            r"setTimeout\s*\(",             # JS (in tests)
            r"time\.Sleep\s*\(",            # Go
            r"std::this_thread::sleep",     # C++
            r"(?<!time\.)(?<!Thread\.)(?<!time\.)sleep\s*\(\s*\d+\s*\)",  # Generic (excludes qualified calls)
        ],
        anti_patterns=[
            r"@pytest\.mark\.timeout",
            r"WebDriverWait",
            r"wait_for",
            r"poll_until",
            r"eventually",
            # CI-aware sleep patterns (from fix templates) are intentional
            r"_ci_mult",
            r"CI.*mult",
            # Small polling intervals in wait loops are intentional
            r"sleep\s*\(\s*0\.\d+\s*\)",
            # Commented-out examples showing "instead of" are not actual code
            r"#\s*Instead of:",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Add CI-aware multiplier to sleep duration",
                templates={
                    "python": """import os as _os
# CI environments are slower — use multiplier
_ci_mult = 3 if _os.environ.get("CI") else 1
time.sleep({duration} * _ci_mult)""",
                    "typescript": """// CI environments are slower — use multiplier
const ciMult = process.env.CI ? 3 : 1;
await sleep({duration} * ciMult);""",
                    "go": """// CI environments are slower — use multiplier
mult := 1
if os.Getenv("CI") != "" {{
    mult = 3
}}
time.Sleep(time.Duration({duration}) * time.Duration(mult) * time.Second)""",
                },
                context_needed=["duration"],
                confidence=0.75,
            ),
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Replace sleep with polling/waiting for condition",
                templates={
                    "python": """# Instead of: time.sleep({duration})
# Wait for condition with timeout
import time
_timeout = {duration} * 2  # Double the original sleep as timeout
_start = time.time()
while time.time() - _start < _timeout:
    if {condition}:
        break
    time.sleep(0.1)  # Small polling interval
else:
    raise TimeoutError("Condition not met within timeout")""",
                    "typescript": """// Instead of: await sleep({duration})
// Wait for condition with timeout
await waitFor(() => {condition}, {{ timeout: {duration} * 2000 }});""",
                    "javascript": """// Instead of: await sleep({duration})
// Wait for condition with timeout
await waitFor(() => {condition}, {{ timeout: {duration} * 2000 }});""",
                    "go": """// Instead of: time.Sleep({duration})
// Wait for condition with timeout
ctx, cancel := context.WithTimeout(context.Background(), {duration}*2)
defer cancel()
for {{
    select {{
    case <-ctx.Done():
        t.Fatal("Condition not met within timeout")
    default:
        if {condition} {{
            return
        }}
        time.Sleep(100 * time.Millisecond)
    }}
}}""",
                },
                context_needed=["condition", "duration"],
                confidence=0.7,
            ),
        ],
        fix_explanation="Hardcoded sleeps cause flakiness because the actual time needed varies. Replace with condition-based waiting.",
        research_citation="Luo et al. 'An Empirical Analysis of Flaky Tests' FSE 2014",
    ),
    FlakyPattern(
        id="async-wait-no-await",
        name="Missing Await",
        category=PatternCategory.ASYNC_WAIT,
        description="Async operation called without await",
        severity=5,
        base_confidence=0.85,
        code_patterns=[
            r"(?<!await\s)fetch\s*\(",                    # JS fetch without await
            r"(?<!await\s)axios\.\w+\s*\(",              # Axios without await
            r"(?<!await\s)prisma\.\w+\.\w+\s*\(",        # Prisma without await
            r"asyncio\.create_task\s*\([^)]+\)(?!\s*\.)",  # Task without storing
        ],
        anti_patterns=[
            r"\.then\s*\(",                               # Promise chain (intentionally not awaited)
            r"fire.and.forget",                           # Intentional fire-and-forget comment
            r"#\s*no.?await",                             # Explicit no-await comment
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Add await keyword",
                templates={
                    "python": "await {expression}",
                    "typescript": "await {expression}",
                    "javascript": "await {expression}",
                },
                context_needed=["expression"],
                confidence=0.9,
            ),
        ],
        fix_explanation="Async operations must be awaited to ensure completion before assertions.",
    ),
    FlakyPattern(
        id="async-wait-fixed-timeout",
        name="Fixed Timeout Value",
        category=PatternCategory.ASYNC_WAIT,
        description="Using fixed timeout that may be too short in CI",
        severity=3,
        base_confidence=0.65,
        code_patterns=[
            r"timeout\s*=\s*[0-5](?!\.\d)\b",   # Very short integer timeout (excludes decimals like 0.1)
            r"waitFor\s*\([^,]+,\s*\d{1,3}\)",  # Short wait
            r"wait_for\s*\([^,]+,\s*timeout\s*=\s*\d\)",
        ],
        anti_patterns=[
            r"CI.*timeout",                       # Already CI-aware timeout
            r"os\.environ.*CI.*timeout",          # CI-conditional timeout
            r"process\.env\.CI",                  # Node CI-conditional
            # Legitimate short timeouts in infrastructure/library calls
            r"subprocess\.run\s*\(",              # subprocess execution timeout
            r"subprocess\.call\s*\(",
            r"subprocess\.check_output\s*\(",
            r"subprocess\.check_call\s*\(",
            r"subprocess\.Popen\s*\(",
            r"httpx\.\w+\s*\(",                   # httpx client timeouts
            r"requests\.\w+\s*\(",                # requests library
            r"aiohttp\.\w+\s*\(",                 # aiohttp client
            r"AsyncClient\s*\(",                  # httpx async client
            r"Client\s*\(",                       # httpx sync client
            r"ClientSession\s*\(",                # aiohttp ClientSession
            r"urllib3",                            # urllib3 pool manager
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Increase timeout for CI environments",
                templates={
                    "python": "timeout={timeout} * (3 if os.environ.get('CI') else 1)",
                    "typescript": "timeout: {timeout} * (process.env.CI ? 3 : 1)",
                },
                context_needed=["timeout"],
                confidence=0.8,
            ),
        ],
        fix_explanation="CI environments are often slower. Increase timeouts for CI.",
    ),
]

ORDER_DEPENDENCY_PATTERNS = [
    FlakyPattern(
        id="order-dep-global-state",
        name="Global State Mutation",
        category=PatternCategory.ORDER_DEPENDENCY,
        description="Test modifies global/module-level state without cleanup",
        severity=5,
        base_confidence=0.80,
        code_patterns=[
            r"^\s*global\s+\w+",                # Python global
            r"os\.environ\s*\[",               # Environment modification
            r"process\.env\.\w+\s*=",          # Node env modification
            r"System\.setProperty\s*\(",       # Java system property
            r"os\.Setenv\s*\(",                # Go env modification
        ],
        anti_patterns=[
            r"@pytest\.fixture.*autouse",
            r"setUp.*tearDown",
            r"beforeEach.*afterEach",
            r"monkeypatch",
            r"mock\.patch",
            # SQLAlchemy/ORM global patterns that are intentional
            r"declarative_base\s*\(",      # Creating base class
            r"registry\s*\(\s*\)",          # SQLAlchemy registry
            r"relationship\s*\(",           # ORM relationship definition
            r"Column\s*\(",                 # Column definition
            r"ForeignKey\s*\(",             # Foreign key definition
            r"@declared_attr",              # Declared attribute decorator
            r"__tablename__\s*=",           # Table name declaration
            r"Base\s*=\s*declarative",      # Base class assignment
            r"mapper_registry",             # Mapper registry
            r"configure_mappers\s*\(",      # Mapper configuration
            r"MetaData\s*\(",               # MetaData creation
            r"Table\s*\(",                  # Table definition
            r"Index\s*\(",                  # Index definition
            r"create_engine\s*\(",          # Engine creation
            r"class\s+\w+\s*\(\s*Base\s*\)", # ORM model definition
            r"@event\.listens_for",         # SQLAlchemy event listener
            r"event\.listen\s*\(",          # Event listener
            # Django ORM patterns
            r"from\s+django\.db\s+import\s+models",  # Django model import
            r"class\s+\w+\s*\(\s*models\.Model\s*\)",  # Django model class
            r"models\.ForeignKey\s*\(",    # Django foreign key
            r"models\.ManyToManyField",    # Django many-to-many
            r"models\.Manager\s*\(",       # Custom manager
            r"@transaction\.atomic",       # Django transaction decorator
            r"django\.db\.connection",     # Django connection
            r"class\s+\w+\s*\(\s*TestCase\s*\)",  # Django TestCase
            r"class\s+\w+\s*\(\s*TransactionTestCase\s*\)",  # Django TransactionTestCase
            r"models\.CharField",          # Django CharField
            r"models\.IntegerField",       # Django IntegerField
            r"models\.TextField",          # Django TextField
            # Peewee ORM patterns
            r"from\s+peewee\s+import",     # Peewee import
            r"class\s+\w+\s*\(\s*Model\s*\)",  # Peewee model (generic)
            r"class\s+\w+\s*\(\s*BaseModel\s*\)",  # Peewee BaseModel
            r"CharField\s*\(",             # Peewee CharField
            r"IntegerField\s*\(",          # Peewee IntegerField
            r"ForeignKeyField\s*\(",       # Peewee foreign key
            r"@database\.atomic",          # Peewee transaction
            r"database\.connect\s*\(",     # Peewee connect
            r"database\.close\s*\(",       # Peewee close
            # Tortoise ORM patterns
            r"from\s+tortoise\s+import",   # Tortoise import
            r"from\s+tortoise\.models\s+import\s+Model",  # Tortoise model import
            r"fields\.IntField",           # Tortoise IntField
            r"fields\.CharField",          # Tortoise CharField
            r"fields\.ForeignKeyField",    # Tortoise ForeignKey
            r"Tortoise\.init\s*\(",        # Tortoise init
            r"Tortoise\.close_connections",  # Tortoise close
            r"init_db\s*\(",               # Common db init function
            r"close_db\s*\(",              # Common db close function
            # Proper cleanup patterns (try/finally, guard objects, etc.)
            r"try\s*:",                           # try/finally cleanup
            r"finally\s*:",                       # finally block
            r"\.uninstall\s*\(",                  # Guard/monkey-patch uninstall
            r"\.restore\s*\(",                    # State restore method
            r"\.undo\s*\(",                       # State undo method
            r"addCleanup\s*\(",                   # unittest cleanup registration
            r"request\.addfinalizer\s*\(",        # pytest finalizer
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use monkeypatch for environment variable mutations",
                templates={
                    "python": """# Instead of: os.environ["KEY"] = value
# Use monkeypatch (auto-restores after test):
# In test: def test_something(monkeypatch):
#     monkeypatch.setenv("KEY", value)
monkeypatch.setenv({expression})""",
                },
                context_needed=["expression"],
                confidence=0.80,
            ),
            FixTemplate(
                strategy=FixStrategy.ADD_CLEANUP,
                description="Add fixture/hook to save and restore state",
                templates={
                    "python": """@pytest.fixture(autouse=True)
def _restore_{var_name}():
    original = {get_expression}
    yield
    {set_expression}""",
                    "typescript": """beforeEach(() => {{
    original{VarName} = {getExpression};
}});
afterEach(() => {{
    {setExpression};
}});""",
                },
                context_needed=["var_name", "get_expression", "set_expression"],
                confidence=0.85,
            ),
        ],
        fix_explanation="Global state must be restored after each test to prevent order-dependent failures.",
        research_citation="Shi et al. 'iFixFlakies' ISSTA 2019",
    ),
    FlakyPattern(
        id="order-dep-shared-db",
        name="Shared Database State",
        category=PatternCategory.ORDER_DEPENDENCY,
        description="Test relies on database state from other tests",
        severity=5,
        base_confidence=0.70,
        code_patterns=[
            r"\.query\s*\(['\"]SELECT",
            r"\.find\s*\(\s*\{",
            r"await\s+prisma\.\w+\.findMany",
            r"session\.query\s*\(",
        ],
        anti_patterns=[
            r"@pytest\.fixture.*scope\s*=\s*['\"]function",
            r"TRUNCATE",
            r"deleteMany",
            r"transaction.*rollback",
            # SQLAlchemy ORM anti-patterns
            r"scoped_session",             # Proper session management
            r"sessionmaker",               # Session factory (not direct state)
            r"Session\s*\(\s*\)",          # Fresh session creation
            r"engine\.begin\s*\(",         # Transaction context
            r"with\s+Session\s*\(",        # Context-managed session
            r"async\s+with\s+.*Session",   # Async session context
            r"@pytest\.fixture.*session",  # Session fixtures
            r"db_session",                 # Common fixture name
            r"test_session",               # Test session fixture
            r"create_all\s*\(",            # Table creation (setup)
            r"drop_all\s*\(",              # Table cleanup
            r"Base\.metadata",             # Declarative base metadata access
            r"\.begin_nested\s*\(",        # Savepoint (proper transaction)
            r"connection\.begin\s*\(",     # Connection-level transaction
            # Django ORM anti-patterns
            r"@transaction\.atomic",       # Django transaction decorator
            r"TransactionTestCase",        # Django test with transaction
            r"TestCase\s*\)",              # Django TestCase (auto-rollback)
            r"django\.test",               # Django test module
            r"call_command\s*\(",          # Django management commands
            r"syncdb|migrate",             # Database setup commands
            r"flush\s*\(",                 # Django flush
            r"\.objects\.all\(\)\.delete", # Bulk delete
            # Peewee ORM anti-patterns
            r"@database\.atomic",          # Peewee transaction
            r"database\.atomic\s*\(",      # Peewee transaction context
            r"\.create_tables\s*\(",       # Peewee table creation
            r"\.drop_tables\s*\(",         # Peewee table cleanup
            r"database\.rollback\s*\(",    # Peewee rollback
            r"database\.commit\s*\(",      # Peewee commit
            # Tortoise ORM anti-patterns
            r"Tortoise\.init\s*\(",        # Tortoise init
            r"Tortoise\.generate_schemas", # Tortoise schema generation
            r"Tortoise\.close_connections",  # Tortoise cleanup
            r"@atomic\s*\(",               # Tortoise atomic decorator
            r"in_transaction\s*\(",        # Tortoise transaction check
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_SETUP,
                description="Add database cleanup/setup fixture",
                templates={
                    "python": """@pytest.fixture(autouse=True)
def clean_db(db_session):
    yield
    db_session.rollback()
    # Or: db_session.execute("TRUNCATE {tables} CASCADE")""",
                    "typescript": """beforeEach(async () => {{
    await prisma.$transaction([
        prisma.{model}.deleteMany(),
    ]);
}});""",
                },
                context_needed=["tables", "model"],
                confidence=0.75,
            ),
        ],
        fix_explanation="Each test should set up its own data and clean up after.",
    ),
    FlakyPattern(
        id="order-dep-file-system",
        name="Shared File System State",
        category=PatternCategory.ORDER_DEPENDENCY,
        description="Test creates/modifies files without cleanup",
        severity=4,
        base_confidence=0.70,
        code_patterns=[
            r"open\s*\([^)]+,\s*['\"]w",
            r"Path\s*\([^)]+\)\.write",
            r"fs\.writeFile",
            r"ioutil\.WriteFile",
            r"os\.Create\s*\(",
        ],
        anti_patterns=[
            r"tmp_path",
            r"tempfile",
            r"mkdtemp",
            r"@pytest\.fixture.*tmp",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use temporary directory fixture",
                templates={
                    "python": """# Use tmp_path fixture instead
def test_something(tmp_path):
    file_path = tmp_path / "test_file.txt"
    file_path.write_text({content})""",
                    "typescript": """// Use temp directory
import {{ mkdtempSync, rmSync }} from 'fs';
import {{ tmpdir }} from 'os';
import {{ join }} from 'path';

let tempDir: string;
beforeEach(() => {{
    tempDir = mkdtempSync(join(tmpdir(), 'test-'));
}});
afterEach(() => {{
    rmSync(tempDir, {{ recursive: true }});
}});""",
                },
                context_needed=["content"],
                confidence=0.9,
            ),
        ],
        fix_explanation="Use temporary directories that are automatically cleaned up.",
    ),
]

CONCURRENCY_PATTERNS = [
    FlakyPattern(
        id="concurrency-race-condition",
        name="Potential Race Condition",
        category=PatternCategory.CONCURRENCY,
        description="Multiple threads/coroutines accessing shared state",
        severity=5,
        base_confidence=0.75,
        code_patterns=[
            r"threading\.Thread\s*\(",
            r"asyncio\.gather\s*\(",
            r"Promise\.all\s*\(",
            r"go\s+func\s*\(",
            r"\.start\s*\(\s*\).*\.start\s*\(\s*\)",
            # Schedule randomness patterns (E4/F-09) -- #1 root cause at Uber (37%)
            r"concurrent\.futures\.ThreadPoolExecutor\s*\(",
            r"concurrent\.futures\.ProcessPoolExecutor\s*\(",
            r"asyncio\.create_task\s*\(",
            r"asyncio\.ensure_future\s*\(",
        ],
        anti_patterns=[
            r"Lock\s*\(\s*\)",
            r"Mutex",
            r"synchronized",
            r"atomic",
            # Additional sync primitives (E4/F-09)
            r"WaitGroup",
            r"sync\.Once",
            r"Event\s*\(\s*\)",
            r"Condition\s*\(\s*\)",
            r"Semaphore\s*\(",
            r"Barrier\s*\(",
            r"as_completed\s*\(",
            r"\.result\s*\(",
            r"await\s+asyncio\.gather",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.WRAP,
                description="Add synchronization primitives",
                templates={
                    "python": """import threading
_lock = threading.Lock()
with _lock:
    {critical_section}""",
                    "go": """var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
{critical_section}""",
                },
                context_needed=["critical_section"],
                confidence=0.6,
            ),
        ],
        fix_explanation="Shared state accessed by multiple threads requires synchronization.",
    ),
    FlakyPattern(
        id="concurrency-assertion-timing",
        name="Assertion Before Async Completion",
        category=PatternCategory.CONCURRENCY,
        description="Asserting immediately after starting async operation",
        severity=4,
        base_confidence=0.80,
        code_patterns=[
            r"\.start\s*\(\s*\)[\s\n]*assert",
            r"asyncio\.create_task[\s\S]{0,50}assert",
            r"go\s+func[\s\S]{0,100}if\s+\w+\s*[!=]=",
        ],
        anti_patterns=[
            r"await\s+asyncio\.gather",           # Properly gathered tasks
            r"\.result\s*\(",                      # Getting task result before assert
            r"<-\s*\w+",                          # Go channel receive (proper sync)
            r"await\s+task",                       # Task properly awaited before assert
            r"result\s*=\s*await",                 # Result captured from await before assert
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Wait for completion before asserting",
                templates={
                    "python": """task = asyncio.create_task({async_call})
result = await task
assert {assertion}""",
                },
                context_needed=["async_call", "assertion"],
                confidence=0.8,
            ),
        ],
        fix_explanation="Must wait for async operations to complete before making assertions.",
    ),
]

TIME_DEPENDENT_PATTERNS = [
    FlakyPattern(
        id="time-dep-wall-clock",
        name="Wall Clock Dependency",
        category=PatternCategory.TIME_DEPENDENT,
        description="Test depends on current time",
        severity=4,
        base_confidence=0.80,
        code_patterns=[
            r"datetime\.now\s*\(\s*\)",
            r"time\.time\s*\(\s*\)",
            r"Date\.now\s*\(\s*\)",
            r"new\s+Date\s*\(\s*\)",
            r"time\.Now\s*\(\s*\)",
            r"System\.currentTimeMillis",
        ],
        anti_patterns=[
            r"freezegun",
            r"freeze_time",
            r"mock.*time",
            r"jest\.useFakeTimers",
            r"sinon\.useFakeTimers",
            # State manipulation — not timing assertions
            r"_last_.*time\s*=",                  # Setting internal timestamp state
            r"_.*_time\s*=\s*time\.time",         # Assigning time to internal field
            r"time\.time\(\)\s*-",                # Subtracting = simulating past time
            r"time\.time\(\)\s*\+",               # Adding = simulating future time
            r"=\s*time\.time\(\)\s*[-+]",         # Assignment with time arithmetic
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_MOCK,
                description="Mock time for deterministic tests",
                templates={
                    "python": """from freezegun import freeze_time

@freeze_time("{fixed_time}")
def test_something():
    # Now datetime.now() returns {fixed_time}
    {test_body}""",
                    "typescript": """beforeEach(() => {{
    jest.useFakeTimers();
    jest.setSystemTime(new Date('{fixed_time}'));
}});
afterEach(() => {{
    jest.useRealTimers();
}});""",
                },
                context_needed=["fixed_time", "test_body"],
                confidence=0.9,
            ),
        ],
        fix_explanation="Tests depending on current time are non-deterministic. Mock time for consistency.",
    ),
    FlakyPattern(
        id="time-dep-timezone",
        name="Timezone Assumption",
        category=PatternCategory.TIME_DEPENDENT,
        description="Test assumes specific timezone",
        severity=3,
        base_confidence=0.70,
        code_patterns=[
            r"\.hour\s*==\s*\d+",
            r"getHours\s*\(\s*\)\s*===?\s*\d+",
            r"strftime.*%H",
        ],
        anti_patterns=[
            r"UTC",
            r"timezone",
            r"tz=",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use UTC explicitly",
                templates={
                    "python": "datetime.now(timezone.utc)",
                    "typescript": "new Date().toISOString()",
                },
                context_needed=[],
                confidence=0.85,
            ),
        ],
        fix_explanation="Always use UTC in tests to avoid timezone-related failures.",
    ),
]

RANDOMNESS_PATTERNS = [
    FlakyPattern(
        id="random-unseeded",
        name="Unseeded Random",
        category=PatternCategory.RANDOMNESS,
        description="Using random without setting seed",
        severity=4,
        base_confidence=0.75,
        code_patterns=[
            r"random\.\w+\s*\(",
            r"Math\.random\s*\(",
            r"rand\.\w+\s*\(",
            r"Random\s*\(\s*\)\.",
        ],
        anti_patterns=[
            r"random\.seed\s*\(",
            r"np\.random\.seed",
            r"torch\.manual_seed",
            r"Random\s*\(\s*\d+\s*\)",
            # Test replay/verification contexts where random is intentional
            r"replay",
            r"deterministic",
            r"mock.*random",
            r"patch.*random",
            r"faker",
            r"factory_boy",
            r"hypothesis",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_SEED,
                description="Add deterministic seed",
                templates={
                    "python": """import random
random.seed(42)  # Use fixed seed for reproducibility
# Or use pytest-randomly plugin for automatic seeding""",
                    "typescript": """// Use seedable random library
import seedrandom from 'seedrandom';
const rng = seedrandom('test-seed');""",
                    "go": """rand.Seed(42) // Fixed seed for reproducibility""",
                },
                context_needed=[],
                confidence=0.9,
            ),
        ],
        fix_explanation="Random values cause non-deterministic tests. Use fixed seeds for reproducibility.",
    ),
]

RESOURCE_LEAK_PATTERNS = [
    FlakyPattern(
        id="resource-unclosed-connection",
        name="Unclosed Connection",
        category=PatternCategory.RESOURCE_LEAK,
        description="Database/network connection not properly closed",
        severity=4,
        base_confidence=0.65,
        code_patterns=[
            r"\.connect\s*\([^)]*\)(?!.*\.close)",
            r"create_connection\s*\(",
            r"psycopg2\.connect\s*\(",
            r"createConnection\s*\(",
            r"sql\.Open\s*\(",
        ],
        anti_patterns=[
            r"with\s+.*connect",
            r"\.close\s*\(",
            r"defer.*Close",
            r"finally.*close",
            r"@contextmanager",
            # SQLAlchemy ORM patterns that handle connection properly
            r"sessionmaker",               # Session factory manages connections
            r"scoped_session",             # Thread-local session management
            r"Session\s*\(",               # Session creation (pools connections)
            r"with\s+Session",             # Context-managed session
            r"async\s+with\s+.*Session",   # Async session context
            r"engine\.connect\s*\(",       # Engine-managed connection
            r"engine\.begin\s*\(",         # Transaction context
            r"connection_pool",            # Pool management
            r"create_async_engine",        # Async engine creation
            r"AsyncSession",               # Async session
            r"\.begin_nested",             # Savepoint (managed)
            r"@pytest\.fixture.*session",  # Test session fixtures
            r"yield\s+session",            # Yielding session in fixture
            # Django ORM connection management
            r"django\.db\.connection",     # Django manages its own connection
            r"connection\.cursor\s*\(",    # Django cursor (managed)
            r"@transaction\.atomic",       # Transaction-managed connection
            r"connection\.ensure_connection",  # Django connection check
            r"connections\[",              # Django multi-db connection
            r"django\.db\.close_old_connections",  # Django cleanup
            r"TestCase",                   # Django TestCase manages connections
            # Peewee ORM connection management
            r"database\.connect\s*\(",     # Peewee connect (usually in fixture)
            r"database\.close\s*\(",       # Peewee close
            r"@database\.connection_context",  # Peewee context manager
            r"database\.is_closed\s*\(",   # Connection state check
            r"PooledDatabase",             # Peewee connection pooling
            r"RetryOperationalError",      # Peewee retry decorator
            # Tortoise ORM connection management
            r"Tortoise\.init\s*\(",        # Tortoise handles connections
            r"Tortoise\.close_connections",  # Tortoise cleanup
            r"init_db\s*\(",               # Common init function
            r"close_db\s*\(",              # Common close function
            r"register_tortoise\s*\(",     # Tortoise registration (FastAPI)
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.WRAP,
                description="Use context manager or try-finally",
                templates={
                    "python": """with {connection_expr} as conn:
    {body}
# Connection automatically closed""",
                    "typescript": """try {{
    const conn = await {connectionExpr};
    {body}
}} finally {{
    await conn.close();
}}""",
                    "go": """conn, err := {connectionExpr}
if err != nil {{
    t.Fatal(err)
}}
defer conn.Close()
{body}""",
                },
                context_needed=["connection_expr", "body"],
                confidence=0.85,
            ),
        ],
        fix_explanation="Unclosed connections can exhaust connection pools, causing test failures.",
    ),
    FlakyPattern(
        id="resource-unclosed-file",
        name="Unclosed File Handle",
        category=PatternCategory.RESOURCE_LEAK,
        description="File opened without proper closing",
        severity=3,
        base_confidence=0.60,
        code_patterns=[
            r"open\s*\([^)]+\)(?!.*\.close)",
            r"fs\.open\s*\(",
            r"os\.Open\s*\(",
        ],
        anti_patterns=[
            r"with\s+open",
            r"\.close\s*\(",
            r"defer.*Close",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.WRAP,
                description="Use context manager",
                templates={
                    "python": """with open({path}, {mode}) as f:
    {body}""",
                    "go": """f, err := os.Open({path})
if err != nil {{
    t.Fatal(err)
}}
defer f.Close()
{body}""",
                },
                context_needed=["path", "mode", "body"],
                confidence=0.9,
            ),
        ],
        fix_explanation="Always close file handles to prevent resource exhaustion.",
    ),
]

NETWORK_PATTERNS = [
    FlakyPattern(
        id="network-external-call",
        name="External Network Call",
        category=PatternCategory.NETWORK,
        description="Test makes real network call to external service",
        severity=4,
        base_confidence=0.85,
        code_patterns=[
            r"requests\.(get|post|put|delete)\s*\(['\"]https?://(?!localhost|127\.0\.0\.1)",
            r"fetch\s*\(['\"]https?://(?!localhost|127\.0\.0\.1)",
            r"axios\.\w+\s*\(['\"]https?://(?!localhost|127\.0\.0\.1)",
            r"http\.Get\s*\(['\"]https?://(?!localhost|127\.0\.0\.1)",
        ],
        anti_patterns=[
            r"@responses\.activate",
            r"@httpretty",
            r"nock\s*\(",
            r"mock.*request",
            r"httptest\.NewServer",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_MOCK,
                description="Mock external HTTP calls",
                templates={
                    "python": """import responses

@responses.activate
def test_something():
    responses.add(
        responses.GET,
        "{url}",
        json={response_body},
        status=200,
    )
    {test_body}""",
                    "typescript": """import nock from 'nock';

beforeEach(() => {{
    nock('{base_url}')
        .get('{path}')
        .reply(200, {responseBody});
}});

afterEach(() => {{
    nock.cleanAll();
}});""",
                },
                context_needed=["url", "response_body", "test_body"],
                confidence=0.8,
            ),
        ],
        fix_explanation="External network calls are unreliable. Mock them for consistent tests.",
    ),
]

FLOATING_POINT_PATTERNS = [
    FlakyPattern(
        id="float-exact-comparison",
        name="Exact Floating Point Comparison",
        category=PatternCategory.FLOATING_POINT,
        description="Using exact equality for floating point numbers",
        severity=3,
        base_confidence=0.75,
        code_patterns=[
            r"assert\s+.+\s*==\s*\d+\.\d+",
            r"assert\s+\d+\.\d+\s*==\s*.+",
            r"expect\s*\([^)]+\)\.toBe\s*\(\s*\d+\.\d+",
            r"assertEqual\s*\([^,]+,\s*\d+\.\d+",
        ],
        anti_patterns=[
            r"pytest\.approx",
            r"assertAlmostEqual",
            r"toBeCloseTo",
            r"tolerance",
            r"epsilon",
            # IEEE-754 exact values — these never have floating point drift
            r"==\s*0\.0\b",
            r"==\s*1\.0\b",
            r"==\s*-?0\.5\b",
            # Function literally returns the constant being compared
            r"return\s+\d+\.\d+",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_TOLERANCE,
                description="Use approximate comparison",
                templates={
                    "python": "assert {actual} == pytest.approx({expected}, rel={tolerance})",
                    "typescript": "expect({actual}).toBeCloseTo({expected}, {decimals})",
                    "go": """if math.Abs({actual} - {expected}) > {tolerance} {{
    t.Errorf("expected %v, got %v", {expected}, {actual})
}}""",
                },
                context_needed=["actual", "expected", "tolerance"],
                confidence=0.95,
            ),
        ],
        fix_explanation="Floating point arithmetic is imprecise. Use approximate comparisons.",
    ),
]

PLATFORM_PATTERNS = [
    FlakyPattern(
        id="platform-path-separator",
        name="Platform-Specific Path Separator",
        category=PatternCategory.PLATFORM,
        description="Using hardcoded path separators instead of os.path or pathlib",
        severity=3,
        base_confidence=0.55,
        code_patterns=[
            # Match file system paths with MULTIPLE path components
            # e.g., "/home/user/file.txt" or "/var/log/app.log"
            # Require at least 2 separators to distinguish from URL routes like "/admin"
            r"['\"](?:/[a-zA-Z_][a-zA-Z0-9_-]*/[a-zA-Z0-9_./\\-]+)['\"]",
            # Match Windows paths: "C:\Users\file.txt"
            r"['\"][A-Z]:\\[a-zA-Z0-9_./\\-]+['\"]",
            # Match paths with file extensions (likely file paths, not URL routes)
            r"['\"](?:/[a-zA-Z_][a-zA-Z0-9_/\\-]*\.[a-zA-Z]{1,5})['\"]",
            r"\.split\s*\(\s*['\"][/\\]['\"]",  # Splitting on path separator
            r"\+\s*['\"][/\\]['\"]",            # Concatenating with separator
        ],
        anti_patterns=[
            # Path library usage (safe)
            r"os\.path\.|os\.sep|pathlib|Path\s*\(",
            # URLs — any protocol scheme
            r"(?:https?|ftp|file|tcp|amqp|redis|mongodb|ws|wss)://",
            # MIME types
            r"['\"](?:application|text|image|audio|video|font|model|multipart|message)/[a-z]+['\"]",
            # HTML tags
            r"</?\w+/?>",
            # Web framework route/request contexts
            r"\.(?:route|get|post|put|delete|patch|request)\s*\(",
            # Test client contexts
            r"(?:client\.|TestClient|test_client)",
            # Framework decorators & URL patterns
            r"@(?:app|router)\.|urlpatterns|(?:LOGIN|LOGOUT)_URL",
            # HTTP path/cookie/header parameters
            r"(?:path|url|route|endpoint)\s*=\s*['\"]|content[-_]?type|\.set_cookie|cookies?\.|headers?\[",
            # API route assertions & response patterns
            r"assert.*['\"]/.*/|response\s*=|['\"]:\s*\{",
            # OpenAPI/Swagger schemas
            r"openapi|swagger|paths\s*[=:]",
            # Test function definitions
            r"(?:async\s+)?def\s+test_|benchmark|_bench",
            # Django URL helpers
            r"assertRedirects|reverse\s*\(|resolve\s*\(",
            # GraphQL contexts
            r"graphql|gql\s*\(|(?:mutation|query|subscription)\s*\{|@(?:strawberry|ariadne)|schema\s*=|type\s+(?:Query|Mutation)\b",
            # WebSocket contexts
            r"websocket|socket\.io|socketio|WebSocket\s*\(|channels\.|@websocket|on_(?:connect|disconnect|message)|broadcast",
            # gRPC contexts
            r"\.proto\b|grpc\.|stub\.|servicer|rpc\s+\w+\s*\(",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use os.path.join or pathlib for cross-platform paths",
                templates={
                    "python": """from pathlib import Path
# Instead of hardcoded paths, use:
path = Path({base}) / {component}""",
                    "go": """import "path/filepath"
// Instead of hardcoded paths, use:
path := filepath.Join({base}, {component})""",
                    "rust": """use std::path::PathBuf;
// Instead of hardcoded paths, use:
let path: PathBuf = [{base}, {component}].iter().collect();""",
                },
                context_needed=["base", "component"],
                confidence=0.85,
            ),
        ],
        fix_explanation="Hardcoded path separators fail on different operating systems. Use platform-agnostic path APIs.",
    ),
    FlakyPattern(
        id="platform-line-ending",
        name="Platform-Specific Line Endings",
        category=PatternCategory.PLATFORM,
        description="Assuming specific line endings (\\n vs \\r\\n)",
        severity=2,  # Reduced severity - less common issue
        base_confidence=0.65,
        code_patterns=[
            r"\.split\s*\(\s*['\"]\\n['\"\s]*\)",  # Splitting on \n only (with closing paren)
            r"\.replace\s*\(\s*['\"]\\r\\n['\"]",  # Replacing \r\n specifically
            r"\.endswith\s*\(\s*['\"]\\n['\"]",   # Checking ends with \n
        ],
        anti_patterns=[
            r"\.splitlines\s*\(",
            r"os\.linesep",
            r"universal_newlines",
            r"newline\s*=",
            r"text\s*=\s*True",                  # Python subprocess text mode
            r"encoding\s*=",                     # Likely handling encoding properly
            # Safe contexts where \n is intentional, not platform-dependent
            r"json\.dumps",                      # JSON always uses \n
            r"json\.loads",                      # JSON parsing
            r'b["\']',                           # Binary string context
            r"\.decode\s*\(",                    # Decoding chains handle encoding
            r"assert.*split\s*\(",              # Assertion on string parsing
            r"textwrap\.",                        # textwrap module uses \n deliberately
            # File-reading contexts where line endings are controlled
            r"read_text\s*\(",                   # pathlib read_text normalizes
            r"git\s+show",                       # git show output uses \n
            r"git\s+log",                        # git log output uses \n
            r"git\s+diff",                       # git diff output uses \n
            r"subprocess.*capture_output",       # subprocess with capture
            r"\.stdout",                         # reading from process stdout
            r"\.communicate\s*\(",               # subprocess communicate
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use platform-agnostic line ending handling",
                templates={
                    "python": """# Instead of: content.split('\\n')
# Use: content.splitlines()
lines = {content}.splitlines()""",
                    "go": """import "strings"
// Use strings.Split with platform-aware separator
// or use bufio.Scanner for line reading""",
                },
                context_needed=["content"],
                confidence=0.9,
            ),
        ],
        fix_explanation="Line endings differ between platforms. Use splitlines() or universal newline handling.",
    ),
    FlakyPattern(
        id="platform-temp-path",
        name="Hardcoded Temp Directory",
        category=PatternCategory.PLATFORM,
        description="Using hardcoded temp directory path like /tmp",
        severity=4,
        base_confidence=0.90,
        code_patterns=[
            r"['\"][/\\]tmp[/\\]",              # /tmp/
            r"['\"]C:[\\]Temp[\\]",             # C:\Temp
            r"['\"]C:[\\]Windows[\\]Temp",      # C:\Windows\Temp
            r"['\"][/\\]var[/\\]tmp",           # /var/tmp
        ],
        anti_patterns=[
            r"tempfile\.",
            r"tmp_path",
            r"os\.environ\.get\s*\(\s*['\"]TEMP",
            r"gettempdir",
            r"TempDir",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use platform-agnostic temp directory APIs",
                templates={
                    "python": """import tempfile
# Instead of hardcoded /tmp, use:
temp_dir = tempfile.gettempdir()
# Or for tests, use tmp_path fixture""",
                    "go": """import "os"
// Instead of hardcoded paths, use:
tempDir := os.TempDir()""",
                    "rust": """use std::env;
// Instead of hardcoded paths, use:
let temp_dir = env::temp_dir();""",
                    "typescript": """import os from 'os';
// Instead of hardcoded paths, use:
const tempDir = os.tmpdir();""",
                },
                context_needed=[],
                confidence=0.95,
            ),
        ],
        fix_explanation="Temp directory locations vary by OS. Use the OS-provided temp directory API.",
    ),
    FlakyPattern(
        id="platform-executable-extension",
        name="Missing Executable Extension",
        category=PatternCategory.PLATFORM,
        description="Running executables without considering .exe on Windows",
        severity=3,
        base_confidence=0.60,
        code_patterns=[
            r"subprocess\.\w+\s*\(\s*\[['\"](?!python|node|npm|npx|git)[a-z_-]+['\"]",
            r"exec\.Command\s*\(['\"](?!python|node|npm|npx|git)[a-z_-]+['\"]",
            r"spawn\s*\(['\"](?!python|node|npm|npx|git)[a-z_-]+['\"]",
        ],
        anti_patterns=[
            r"shutil\.which",
            r"exec\.LookPath",
            r"which\s*\(",
            r"\.exe",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use which/LookPath to find executable",
                templates={
                    "python": """import shutil
# Find executable path for cross-platform support
exe_path = shutil.which("{executable}")
if exe_path is None:
    raise FileNotFoundError("{executable} not found in PATH")
subprocess.run([exe_path, ...])""",
                    "go": """import "os/exec"
// Use LookPath to find executable
exePath, err := exec.LookPath("{executable}")
if err != nil {{
    return err
}}
cmd := exec.Command(exePath, ...)""",
                },
                context_needed=["executable"],
                confidence=0.8,
            ),
        ],
        fix_explanation="Executable names differ on Windows (.exe). Use which/LookPath to find the correct path.",
    ),
]

ENVIRONMENT_PATTERNS = [
    FlakyPattern(
        id="env-ci-detection",
        name="CI Environment Detection",
        category=PatternCategory.ENVIRONMENT,
        description="Test behavior depends on CI environment variables without fallback",
        severity=4,
        base_confidence=0.75,
        code_patterns=[
            r"os\.environ\[.CI.\]",
            r"os\.getenv\(.CI.\)",
            r"os\.environ\.get\(.GITHUB_ACTIONS.\)",
            r"os\.environ\[.JENKINS.\]",
            r"os\.environ\[.TRAVIS.\]",
            r"os\.environ\[.GITLAB_CI.\]",
            r"process\.env\.CI\b",
            r"System\.getenv\(.CI.\)",
        ],
        anti_patterns=[
            r"os\.environ\.get\(.CI.,\s*.false.\)",
            r"os\.environ\.get\(.CI.,\s*False\)",
            r"os\.getenv\(.CI.,\s*None\)",
            r"os\.getenv\(.CI.,\s*False\)",
            r"skipIf.*CI",
            r"pytest\.mark\.skipif",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.WRAP,
                description="Use safe environment variable access with defaults",
                templates={
                    "python": 'is_ci = os.environ.get("CI", "false").lower() == "true"',
                    "javascript": 'const isCI = process.env.CI === "true";',
                },
                confidence=0.8,
            ),
        ],
        fix_explanation="CI environment variables may not exist locally. Use safe access with defaults.",
    ),
    FlakyPattern(
        id="env-locale-dependent",
        name="Locale-Dependent Test",
        category=PatternCategory.ENVIRONMENT,
        description="Test depends on system locale for string formatting, sorting, or date formatting",
        severity=3,
        base_confidence=0.65,
        code_patterns=[
            r"locale\.setlocale\(",
            r"locale\.getlocale\(",
            r"strftime\s*\(",
            r"Collator\(",
            r"localeCompare\(",
            r"\.toLocaleString\(",
            r"\.toLocaleDateString\(",
            r"NumberFormat\(",
            r"DateTimeFormatter\.ofLocalizedDate",
        ],
        anti_patterns=[
            r"locale\.setlocale\(locale\.LC_ALL,\s*['\"]C",
            r"locale\.setlocale\(locale\.LC_ALL,\s*['\"]en_US",
            r"Locale\.US",
            r"Locale\.ENGLISH",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.ADD_SETUP,
                description="Set explicit locale in test setup",
                templates={
                    "python": 'locale.setlocale(locale.LC_ALL, "C")',
                    "java": "Locale.setDefault(Locale.US);",
                },
                confidence=0.7,
            ),
        ],
        fix_explanation="System locale varies between environments. Set an explicit locale in tests.",
    ),
    FlakyPattern(
        id="env-docker-specific",
        name="Docker/Container-Specific Behavior",
        category=PatternCategory.ENVIRONMENT,
        description="Test assumes container-specific filesystem, networking, or resource limits",
        severity=3,
        base_confidence=0.60,
        code_patterns=[
            r"/proc/\d+/",
            r"/sys/fs/cgroup",
            r"cgroup",
            r"docker\.sock",
            r"DOCKER_HOST",
            r"KUBERNETES_SERVICE_HOST",
            r"containerized",
        ],
        anti_patterns=[
            r"skip.*docker",
            r"skip.*container",
            r"pytest\.mark\.docker",
            # Guard functions that check for container environment are intentional
            r"def\s+is_in_container",
            r"os\.environ\.get\s*\(.*KUBERNETES",
            r"os\.path\.exists\s*\(.*dockerenv",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.WRAP,
                description="Guard container-specific code with availability check",
                templates={
                    "python": """import os
def is_in_container():
    return os.path.exists("/.dockerenv") or os.environ.get("KUBERNETES_SERVICE_HOST")""",
                },
                confidence=0.6,
            ),
        ],
        fix_explanation="Container-specific paths and features may not exist on bare-metal hosts.",
    ),
]

UNORDERED_COLLECTION_PATTERNS = [
    FlakyPattern(
        id="order-dep-unordered-iteration",
        name="Unordered Collection Iteration",
        category=PatternCategory.ORDER_DEPENDENCY,
        base_confidence=0.80,
        description=(
            "Iterating over dicts or sets without sorting. Iteration order of "
            "sets is non-deterministic, and while dict order is insertion-order "
            "in CPython 3.7+, tests that depend on it are fragile. "
            "Accounts for ~33% of flaky tests at Uber (FlakyGuard 2025)."
        ),
        severity=4,
        code_patterns=[
            r"for\s+\w+\s+in\s+\w+\.keys\s*\(\s*\)",    # for k in d.keys()
            r"for\s+\w+\s+in\s+\w+\.values\s*\(\s*\)",   # for v in d.values()
            r"for\s+\w+\s+in\s+\w+\.items\s*\(\s*\)",    # for k,v in d.items()
            r"for\s+\w+\s+in\s+set\s*\(",                 # for x in set(...)
            r"list\s*\(\s*set\s*\(",                       # list(set(...))
            r"for\s+\w+\s+in\s+\{[^}]+\}",               # for x in {literal set}
        ],
        anti_patterns=[
            r"sorted\s*\(",
            r"OrderedDict",
            r"order.*not.*matter",
            r"#\s*order.independent",
            r"assertCountEqual",
            r"assertItemsEqual",
            r"set\s*\(\s*result\s*\)\s*==\s*set",        # set comparison (order-agnostic)
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Sort before iterating for deterministic order",
                templates={
                    "python": """# Instead of: for item in collection:
# Use sorted for deterministic iteration:
for item in sorted({collection}):
    {body}""",
                },
                context_needed=["collection", "body"],
                confidence=0.85,
            ),
        ],
        fix_explanation=(
            "Iteration over dicts/sets can produce non-deterministic order. "
            "Sort before iterating or use order-independent assertions."
        ),
        research_citation="Uber FlakyGuard 2025; Lam et al. 'iDFlakies' ICSE 2019",
    ),
]

SHARED_MUTABLE_STATE_PATTERNS = [
    FlakyPattern(
        id="order-dep-shared-mutable-state",
        name="Shared Mutable State",
        category=PatternCategory.ORDER_DEPENDENCY,
        base_confidence=0.55,
        description=(
            "Class-level or module-level mutable variables (lists, dicts) that "
            "are modified during tests. This causes test order dependency because "
            "mutations persist across test methods."
        ),
        severity=5,
        code_patterns=[
            # Class-level mutable defaults (not instance-level self.xxx)
            r"^\s+\w+\s*=\s*\[\s*\]",                     # cls_var = []
            r"^\s+\w+\s*=\s*\{\s*\}",                     # cls_var = {}
            r"^\s+\w+\s*:\s*(?:List|Dict|Set)\s*=",       # cls_var: List = ...
            # Module-level mutable state modified in functions/methods
            r"^[A-Z_]+\s*=\s*\[\s*\]",                    # MODULE_VAR = []
            r"^[A-Z_]+\s*=\s*\{\s*\}",                    # MODULE_VAR = {}
            # Appending/modifying class or module state
            r"\w+\.append\s*\(",                           # some_list.append(...)
            r"\w+\.extend\s*\(",                           # some_list.extend(...)
            r"\w+\.update\s*\(",                           # some_dict.update(...)
            r"^[A-Z_][A-Z_0-9]*\s*\[\s*['\"]",            # MODULE_DICT["key"] = ... (UPPER_CASE only)
        ],
        anti_patterns=[
            r"self\.\w+\s*=\s*\[",                        # Instance-level init (safe)
            r"self\.\w+\s*=\s*\{",                        # Instance-level init (safe)
            r"self\.\w+\.append",                          # Instance-level mutation (safe)
            r"@pytest\.fixture.*autouse",                  # Auto-cleanup fixture
            r"setUp\s*\(",                                  # setUp method
            r"copy\s*\(\s*\)",                             # Copying before use
            r"deepcopy\s*\(",                              # Deep copy
            r"\.clear\s*\(",                               # Clearing state
            # Local variable patterns inside test functions (not shared state)
            r"def\s+test_\w+.*:\s*\n\s+\w+\s*=\s*\[\]",  # test func with local list init
            r"results?\s*=\s*\[\]",                         # Common local accumulator names
            r"expected\s*=\s*\[",                           # Expected value construction
            r"items\s*=\s*\[",                              # Local items list
            r"errors?\s*=\s*\[",                            # Local errors list
            r"collected\s*=\s*\[",                          # Local collected list
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Copy shared mutable state before use",
                templates={
                    "python": """import copy
# Instead of mutating shared state directly, work on a copy:
# {expression}
# Use: local_copy = copy.copy(shared_var)  # then mutate local_copy""",
                },
                context_needed=["expression"],
                confidence=0.70,
            ),
            FixTemplate(
                strategy=FixStrategy.ADD_SETUP,
                description="Use per-test fixtures or instance variables instead of shared state",
                templates={
                    "python": """@pytest.fixture(autouse=True)
def _reset_shared_state():
    \"\"\"Reset shared mutable state before each test.\"\"\"
    original = {shared_var}.copy()
    yield
    {shared_var}.clear()
    {shared_var}.update(original)  # or .extend(original) for lists""",
                },
                context_needed=["shared_var"],
                confidence=0.80,
            ),
        ],
        fix_explanation=(
            "Shared mutable state (class-level lists/dicts) persists across tests. "
            "Use instance variables (self.xxx), fixtures with cleanup, or copy the "
            "state before mutation."
        ),
        research_citation="Shi et al. 'iFixFlakies' ISSTA 2019; Gruber et al. 2021",
    ),
]

TEST_INFRASTRUCTURE_PATTERNS = [
    FlakyPattern(
        id="infra-parametrize-mutable-default",
        name="Parametrize with Mutable Default",
        category=PatternCategory.ENVIRONMENT,
        description=(
            "Using mutable defaults in @pytest.mark.parametrize can cause "
            "cross-test pollution when one parametrized case mutates the arg."
        ),
        severity=3,
        base_confidence=0.75,
        code_patterns=[
            r"@pytest\.mark\.parametrize\s*\([^)]*\[\s*\[\]",
            r"@pytest\.mark\.parametrize\s*\([^)]*\[\s*\{\}",
        ],
        anti_patterns=[
            r"copy\s*\(",
            r"deepcopy\s*\(",
            r"freeze\s*\(",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Use immutable defaults or copy in fixture",
                templates={
                    "python": """# Instead of mutable default in parametrize,
# use a factory fixture or copy:
@pytest.fixture
def {param_name}(request):
    import copy
    return copy.deepcopy(request.param)""",
                },
                context_needed=["param_name"],
                confidence=0.80,
            ),
        ],
        fix_explanation=(
            "Mutable defaults in parametrize are shared across tests. "
            "Use deepcopy or factory fixtures."
        ),
        research_citation="Gruber et al. 2021 -- 28% of Python flakiness from test infrastructure",
    ),
    FlakyPattern(
        id="infra-session-scoped-mutation",
        name="Session/Module-Scoped Fixture Mutation",
        category=PatternCategory.ENVIRONMENT,
        description=(
            "Session or module-scoped fixtures that are mutated by tests "
            "cause order-dependent failures under parallel execution."
        ),
        severity=4,
        base_confidence=0.70,
        code_patterns=[
            r"@pytest\.fixture\s*\(\s*scope\s*=\s*['\"](?:module|session)['\"]",
        ],
        anti_patterns=[
            r"freeze\s*\(",
            r"readonly",
            r"@pytest\.fixture\s*\(\s*scope\s*=\s*['\"]function",
            r"deepcopy\s*\(",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Narrow fixture scope or make returned data immutable",
                templates={
                    "python": """# If fixture data is mutated, narrow scope:
@pytest.fixture(scope="function")  # was module/session
def {fixture_name}():
    return {factory_call}""",
                },
                context_needed=["fixture_name", "factory_call"],
                confidence=0.70,
            ),
        ],
        fix_explanation=(
            "Session/module fixtures persist across tests. If tests mutate "
            "the returned data, narrow scope to 'function' or freeze the data."
        ),
        research_citation="Gruber et al. 2021 -- test infrastructure causes 28% of Python flakiness",
    ),
    FlakyPattern(
        id="infra-xfail-masks-flakiness",
        name="xfail(strict=False) Masks Flakiness",
        category=PatternCategory.ENVIRONMENT,
        description=(
            "Using @pytest.mark.xfail without strict=True can mask flaky "
            "tests: a test that intermittently passes will silently count "
            "as an expected failure."
        ),
        severity=2,
        base_confidence=0.70,
        code_patterns=[
            r"@pytest\.mark\.xfail(?!\s*\([^)]*strict\s*=\s*True)",
        ],
        anti_patterns=[
            r"strict\s*=\s*True",
        ],
        fix_templates=[
            FixTemplate(
                strategy=FixStrategy.REPLACE,
                description="Add strict=True so unexpected passes are flagged",
                templates={
                    "python": '@pytest.mark.xfail(strict=True, reason="{reason}")',
                },
                context_needed=["reason"],
                confidence=0.85,
            ),
        ],
        fix_explanation=(
            "xfail without strict=True silently ignores tests that pass "
            "intermittently, masking underlying flakiness."
        ),
        research_citation="Gruber et al. 2021",
    ),
]

# Combine all patterns
BUILTIN_PATTERNS: List[FlakyPattern] = [
    *ASYNC_WAIT_PATTERNS,
    *ORDER_DEPENDENCY_PATTERNS,
    *CONCURRENCY_PATTERNS,
    *TIME_DEPENDENT_PATTERNS,
    *RANDOMNESS_PATTERNS,
    *RESOURCE_LEAK_PATTERNS,
    *NETWORK_PATTERNS,
    *FLOATING_POINT_PATTERNS,
    *PLATFORM_PATTERNS,
    *ENVIRONMENT_PATTERNS,
    *UNORDERED_COLLECTION_PATTERNS,
    *SHARED_MUTABLE_STATE_PATTERNS,
    *TEST_INFRASTRUCTURE_PATTERNS,
]

# Index by ID for quick lookup
PATTERNS_BY_ID: Dict[str, FlakyPattern] = {p.id: p for p in BUILTIN_PATTERNS}

# Index by category
PATTERNS_BY_CATEGORY: Dict[PatternCategory, List[FlakyPattern]] = {}
for pattern in BUILTIN_PATTERNS:
    if pattern.category not in PATTERNS_BY_CATEGORY:
        PATTERNS_BY_CATEGORY[pattern.category] = []
    PATTERNS_BY_CATEGORY[pattern.category].append(pattern)
