"""Checker for webhook signature verification bypass patterns."""

import re
from pathlib import Path
from typing import List

from stablestack.checkers.base import BaseChecker
from stablestack.models import Finding, Severity, Tier


class WebhookSecurityBypassChecker(BaseChecker):
    """Detects patterns that skip webhook signature verification."""

    rule_id = "SEC006"
    rule_name = "webhook-security-bypass"
    description = "Webhook signature verification should never be skipped"
    why = (
        "Webhook endpoints receive data from external services (Stripe, GitHub, etc.). "
        "Without signature verification, attackers can forge webhook payloads to trigger "
        "unauthorized actions like fake payments or deployments. Patterns that skip "
        "verification 'for development' often accidentally ship to production."
    )
    severity = Severity.ERROR
    tier = Tier.CRITICAL
    file_extensions = {".js", ".ts", ".jsx", ".tsx", ".py"}

    # Patterns that indicate skipping webhook verification
    _BYPASS_PATTERNS = [
        # JavaScript/TypeScript patterns
        (
            re.compile(
                r'if\s*\(\s*!?\s*(webhook_?secret|signing_?secret|WEBHOOK_SECRET|SIGNING_SECRET)\s*\)\s*\{[^}]*'
                r'(return\s+true|skip|bypass|continue)',
                re.IGNORECASE | re.DOTALL
            ),
            "Webhook verification skipped when secret is missing"
        ),
        (
            re.compile(
                r'(process\.env\.|import\.meta\.env\.)\w*(WEBHOOK|SIGNING)\w*\s*\|\|\s*[\'"]',
                re.IGNORECASE
            ),
            "Webhook secret has a fallback default value"
        ),
        (
            re.compile(
                r'if\s*\(\s*(process\.env\.NODE_ENV|NODE_ENV)\s*[!=]==?\s*[\'"]development[\'"]\s*\)'
                r'[^}]*return\s+true',
                re.IGNORECASE | re.DOTALL
            ),
            "Webhook verification bypassed in development mode"
        ),
        (
            re.compile(
                r'//\s*(skip|bypass|disable)\s*(webhook|signature)\s*(verification|validation|check)',
                re.IGNORECASE
            ),
            "Comment indicates intentional security bypass"
        ),
        (
            re.compile(
                r'verify\w*signature\s*=\s*false',
                re.IGNORECASE
            ),
            "Signature verification explicitly disabled"
        ),
        # Stripe-specific — only flag if the catch block returns success (200/true/next()),
        # NOT if it returns an error (400/401/false) which is correct behavior.
        (
            re.compile(
                r'stripe\.webhooks\.constructEvent.*catch.*\{[^}]*(?:return\s+(?:true|next\(\)|NextResponse\.json\([^)]*\{[^}]*status:\s*200))',
                re.IGNORECASE | re.DOTALL
            ),
            "Stripe webhook verification error is caught but processing continues"
        ),
        # Python patterns
        (
            re.compile(
                r'if\s+not\s+(webhook_?secret|signing_?secret):\s*\n\s*(return\s+True|pass|continue)',
                re.IGNORECASE
            ),
            "Webhook verification skipped when secret is missing (Python)"
        ),
        (
            re.compile(
                r'os\.environ\.get\([\'"].*WEBHOOK.*[\'"]\s*,\s*[\'"][^\'"]+[\'"]\)',
                re.IGNORECASE
            ),
            "Webhook secret has a hardcoded default value"
        ),
    ]

    # Warning patterns (less severe but worth noting)
    _WARNING_PATTERNS = [
        (
            re.compile(
                r'console\.(log|warn)\s*\([^)]*skip.*verif',
                re.IGNORECASE
            ),
            "Logging suggests verification may be skipped"
        ),
        (
            re.compile(
                r'TODO:?\s*(add|implement|enable)\s*(webhook|signature)\s*(verification|validation)',
                re.IGNORECASE
            ),
            "TODO indicates webhook verification not yet implemented"
        ),
    ]

    def should_skip_file(self, path: Path) -> bool:
        """Skip test files."""
        if 'test' in path.stem.lower() or 'spec' in path.stem.lower():
            return True
        if 'mock' in path.stem.lower():
            return True
        return super().should_skip_file(path)

    def check_file(self, path: Path, content: str) -> List[Finding]:
        findings = []
        lines = content.splitlines()

        # Only check files that look like webhook handlers
        if not self._is_webhook_file(path, content):
            return []

        # Check for bypass patterns
        for pattern, description in self._BYPASS_PATTERNS:
            for match in pattern.finditer(content):
                line_num = content[:match.start()].count('\n') + 1
                line_content = lines[line_num - 1] if line_num <= len(lines) else ""

                # Skip if in a comment block (but not comment that's the finding)
                if 'Comment indicates' not in description:
                    stripped = line_content.lstrip()
                    if stripped.startswith('//') or stripped.startswith('#') or stripped.startswith('/*'):
                        if 'skip' not in stripped.lower() and 'bypass' not in stripped.lower():
                            continue

                findings.append(
                    self.create_finding(
                        path=path,
                        line=line_num,
                        column=match.start() - content.rfind('\n', 0, match.start()),
                        code=line_content.strip()[:80] + ("..." if len(line_content.strip()) > 80 else ""),
                        message=f"Security bypass detected: {description}",
                        fix_hint=self._get_fix_hint(description),
                    )
                )

        # Check for warning patterns (these get WARNING severity)
        for pattern, description in self._WARNING_PATTERNS:
            for match in pattern.finditer(content):
                line_num = content[:match.start()].count('\n') + 1
                line_content = lines[line_num - 1] if line_num <= len(lines) else ""

                # Create a modified finding with WARNING severity
                finding = self.create_finding(
                    path=path,
                    line=line_num,
                    column=match.start() - content.rfind('\n', 0, match.start()),
                    code=line_content.strip()[:80] + ("..." if len(line_content.strip()) > 80 else ""),
                    message=f"Potential security issue: {description}",
                    fix_hint=self._get_fix_hint(description),
                )
                finding.severity = Severity.WARNING
                findings.append(finding)

        return findings

    def _is_webhook_file(self, path: Path, content: str) -> bool:
        """Check if this file is likely a webhook handler."""
        # Check filename
        webhook_names = ['webhook', 'hook', 'callback', 'stripe', 'github', 'slack', 'twilio', 'resend']
        if any(name in path.stem.lower() for name in webhook_names):
            return True

        # Check content for webhook-related code
        webhook_indicators = [
            r'webhook',
            r'constructEvent',
            r'verify.*signature',
            r'signing.*secret',
            r'x-.*-signature',
            r'svix',
        ]
        for indicator in webhook_indicators:
            if re.search(indicator, content, re.IGNORECASE):
                return True

        return False

    def _get_fix_hint(self, description: str) -> str:
        """Generate fix hints based on the type of bypass detected."""
        base_hint = (
            "Webhook signature verification is critical for security:\n\n"
            "1. NEVER skip verification, even in development:\n"
            "   - Use tools like ngrok or localtunnel for local testing\n"
            "   - Configure test webhook secrets in .env.local\n\n"
            "2. Fail loudly when secret is missing:\n"
            "   if (!WEBHOOK_SECRET) {\n"
            "     throw new Error('WEBHOOK_SECRET is required');\n"
            "   }\n\n"
        )

        if 'Stripe' in description:
            return base_hint + (
                "3. For Stripe webhooks:\n"
                "   const sig = request.headers['stripe-signature'];\n"
                "   const event = stripe.webhooks.constructEvent(\n"
                "     body, sig, process.env.STRIPE_WEBHOOK_SECRET\n"
                "   );\n"
                "   // constructEvent throws if verification fails - don't catch and ignore!"
            )
        elif 'development' in description.lower():
            return base_hint + (
                "3. For local development:\n"
                "   - Use Stripe CLI: stripe listen --forward-to localhost:3000/webhook\n"
                "   - Use ngrok: ngrok http 3000\n"
                "   - Set WEBHOOK_SECRET in .env.local to the test secret"
            )
        elif 'default' in description.lower() or 'fallback' in description.lower():
            return base_hint + (
                "3. Never use default/fallback values for secrets:\n"
                "   // BAD:\n"
                "   const secret = process.env.WEBHOOK_SECRET || 'default';\n\n"
                "   // GOOD:\n"
                "   const secret = process.env.WEBHOOK_SECRET;\n"
                "   if (!secret) throw new Error('WEBHOOK_SECRET required');"
            )

        return base_hint + (
            "3. Always verify signatures before processing:\n"
            "   import { Webhook } from 'svix';\n"
            "   const wh = new Webhook(WEBHOOK_SECRET);\n"
            "   const payload = wh.verify(body, headers);\n"
            "   // Process payload only after verification succeeds"
        )
