#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Fact Parser: Extract declarative facts from natural language (Symposium P0)

Captures: "X is Y", "we use X", "deployed on X", "purchased on Porkbun"
These are the facts that fall through the wants_parser (action-oriented) pipeline.

Dependencies: Python stdlib only (Law 4)
"""

import re
from datetime import datetime, timezone
from typing import Dict, List

from pulse.brain.extraction.wants_classify import _classify_domain

# === FACT PATTERNS ===
# Each tuple: (compiled_regex, fact_type)
# Capture group 1 = the fact payload

FACT_PATTERNS = [
    # Infrastructure: "deployed on Vercel", "hosted at AWS", "deployed the site on Docker"
    (re.compile(
        r"(?:deployed|hosted|running|installed|launched|live)\s+(?:\w+\s+)*?(?:on|at|to|via|with)\s+(.{3,80})",
        re.IGNORECASE), "infrastructure"),

    # Service use: "using Stripe for payments", "we use GitHub Actions"
    (re.compile(
        r"(?:using|we use|we're using|currently using|switched to using)\s+(.{3,80}?)(?:\s+for\s+.{3,60})?(?:\.|,|$)",
        re.IGNORECASE), "service"),

    # Account/purchase: "purchased on Porkbun", "registered at Namecheap"
    (re.compile(
        r"(?:purchased|registered|bought|signed up|subscribed)\s+(?:on|at|with|via|through)\s+(.{3,80})",
        re.IGNORECASE), "account"),

    # Identity: "package name is pulse-os", "called PULSE", "repo is Honesty0x/PULSE"
    (re.compile(
        r"(?:package\s+(?:name\s+)?is|called|named|known as|repo\s+is|project\s+is)\s+(.{2,80})",
        re.IGNORECASE), "identity"),

    # Version: "version 10.15.0", "upgraded to v10", "running Python 3.10"
    (re.compile(
        r"(?:version|v)\s*(\d+\.\d+(?:\.\d+)?(?:\S*))(?:\s|,|\.|$)",
        re.IGNORECASE), "version"),
    (re.compile(
        r"(?:upgraded|updated|bumped|migrated)\s+to\s+(.{3,60})",
        re.IGNORECASE), "version"),

    # Config: "configured to use", "set to production", "pointed at localhost"
    (re.compile(
        r"(?:configured|set|pointed)\s+(?:to|at|for)\s+(.{3,80})",
        re.IGNORECASE), "config"),

    # Tool preference: "we chose React", "prefer Tailwind", "went with Flask"
    (re.compile(
        r"(?:we chose|we picked|prefer|went with|decided on|settled on)\s+(.{3,60})",
        re.IGNORECASE), "tool_preference"),

    # Financial: "plan is Solo", "costs $29", "paying for Pro"
    (re.compile(
        r"(?:plan is|costs?|paying|subscription is|pricing is|tier is)\s+(.{2,60})",
        re.IGNORECASE), "financial"),

    # Integration: "connected to OpenClaw", "integrated with Slack", "hooked into"
    (re.compile(
        r"(?:connected|integrated|hooked|wired|plugged)\s+(?:to|into|with)\s+(.{3,60})",
        re.IGNORECASE), "integration"),

    # Domain/URL: "domain is pulseos.dev", "website at pulseos.dev"
    (re.compile(
        r"(?:domain\s+is|website\s+(?:is|at)|url\s+is|site\s+is)\s+(.{3,60})",
        re.IGNORECASE), "identity"),
]

# Speculative/uncertain markers — skip these
_SPECULATIVE = re.compile(
    r"^(?:maybe|perhaps|might|could|should we|what if|i wonder|not sure|thinking about)",
    re.IGNORECASE
)

# Sentiment-only (not a fact)
_SENTIMENT_ONLY = re.compile(
    r"^(?:\w+\s+)?(?:sucks|is (?:awful|terrible|amazing|great|bad|broken|trash))\s*$",
    re.IGNORECASE
)

# Past tense markers — tag as historical
_PAST_TENSE = re.compile(
    r"(?:used to|previously|formerly|back when|before we|we had|was using|were using)",
    re.IGNORECASE
)

# Named entity heuristic: capitalized word, URL, version, or known service
_ENTITY_PATTERN = re.compile(
    r"(?:[A-Z][a-zA-Z0-9]{2,}|v?\d+\.\d+|\w+\.\w+\.\w+|https?://\S+)"
)

# Known services (boost confidence when matched)
KNOWN_SERVICES = {
    "vercel", "netlify", "heroku", "aws", "gcp", "azure", "docker", "kubernetes",
    "github", "gitlab", "bitbucket", "stripe", "paypal", "porkbun", "namecheap",
    "godaddy", "cloudflare", "slack", "discord", "openai", "anthropic", "google",
    "npm", "pypi", "pip", "react", "vue", "angular", "flask", "fastapi", "django",
    "postgresql", "mysql", "redis", "mongodb", "sqlite", "tailwind", "vite",
    "playwright", "brave", "chrome", "firefox", "ollama", "openclaw",
}


def _extract_entities(text: str) -> List[str]:
    """Extract named entities from text (heuristic, no NLP library)."""
    entities = []
    for match in _ENTITY_PATTERN.finditer(text):
        entity = match.group(0).strip(".,;:()")
        if len(entity) >= 2 and entity.lower() not in ("the", "and", "for", "with", "from"):
            entities.append(entity)
    # Also check for known services (case-insensitive)
    for word in text.lower().split():
        word = word.strip(".,;:()'\"")
        if word in KNOWN_SERVICES and word.title() not in entities:
            entities.append(word.title())
    return list(dict.fromkeys(entities))  # deduplicate preserving order


def parse_facts(text: str) -> List[Dict]:
    """
    Extract declarative facts from text.

    Returns list of fact dicts with fact, fact_type, entities, domain, temporal, confidence.
    Only returns facts with at least 1 named entity (quality gate).
    """
    if not text or len(text) < 10:
        return []

    facts = []
    seen_fingerprints = set()

    for pattern, fact_type in FACT_PATTERNS:
        for match in pattern.finditer(text):
            payload = match.group(1).strip().rstrip(".,;:!?")

            # Quality gates
            if len(payload) < 3 or len(payload) > 300:
                continue
            # Single-word payloads OK if it's a known service, otherwise need 2+ words
            if len(payload.split()) < 2:
                if payload.lower().strip(".,;:()") not in KNOWN_SERVICES:
                    continue

            # Skip speculative statements
            full_match = match.group(0).strip()
            # Check 30 chars before match for speculative context
            start = max(0, match.start() - 30)
            context = text[start:match.start()].strip()
            if _SPECULATIVE.search(context) or _SPECULATIVE.search(full_match):
                continue

            # Skip sentiment-only
            if _SENTIMENT_ONLY.match(payload):
                continue

            # Must contain a named entity
            entities = _extract_entities(payload)
            if not entities:
                # Check full match context for entities
                entities = _extract_entities(full_match)
            if not entities:
                continue

            # Determine temporal scope
            temporal = "current"
            if _PAST_TENSE.search(text[max(0, match.start() - 50):match.end()]):
                temporal = "historical"

            # Confidence: higher for known services, lower for generic
            confidence = 0.7
            if any(e.lower() in KNOWN_SERVICES for e in entities):
                confidence = 0.85

            # Dedup within this parse call
            fp = f"{fact_type}:{payload[:50].lower()}"
            if fp in seen_fingerprints:
                continue
            seen_fingerprints.add(fp)

            facts.append({
                "fact": full_match.strip()[:500],
                "fact_type": fact_type,
                "entities": entities[:5],
                "domain": _classify_domain(payload.lower()),
                "temporal": temporal,
                "confidence": confidence,
                "timestamp": datetime.now(timezone.utc).isoformat(),
            })

    # Cap per interaction
    return facts[:20]


# === CLI ===

if __name__ == "__main__":
    import sys
    import json

    if len(sys.argv) < 2:
        print("Usage: python fact_parser.py <text>")
        print("       python fact_parser.py --backfill")
        sys.exit(1)

    if sys.argv[1] == "--backfill":
        print("Backfill mode not yet implemented. Run retroactive_llm_extractor.py instead.")
        sys.exit(0)

    text = " ".join(sys.argv[1:])
    results = parse_facts(text)
    print(json.dumps(results, indent=2))
    print(f"\n{len(results)} facts extracted.")
