"""Built-in rule sets.

Two sources of rules exist:

1. **Stored rules** - rows in the ``rules`` table, editable from the dashboard.
2. **Derived rules** - generated on the fly from a campaign's targeting fields.
   They are only used when a campaign has no stored scoring rules of its own, so
   a freshly created campaign scores sensibly before the user writes any rules.

``exclude_keywords`` always produces reject rules, because a user who typed an
exclusion into the campaign form expects it to be honoured unconditionally.
"""

from __future__ import annotations

from typing import Any

from app.models.enums import RuleAction, RuleOperator
from app.rules.engine import RuleSpec

#: Points awarded by derived campaign rules.
DERIVED_POINTS = {
    "title": 30,
    "industry": 25,
    "location": 20,
    "company_size": 15,
    "keyword": 10,
}

#: Global guard rails seeded into the rules table on first run.
GLOBAL_RULE_SEEDS: list[dict[str, Any]] = [
    {
        "name": "Reject students",
        "field": "jobTitle",
        "operator": RuleOperator.REGEX,
        "value": r"\bstudent\b",
        "action": RuleAction.REJECT,
        "action_value": "Job title indicates a student",
        "priority": 10,
        "description": "Students are not buyers for our services.",
    },
    {
        "name": "Reject interns",
        "field": "jobTitle",
        "operator": RuleOperator.REGEX,
        "value": r"\bintern\b",
        "action": RuleAction.REJECT,
        "action_value": "Job title indicates an intern",
        "priority": 11,
        "description": "Interns have no purchasing authority.",
    },
    {
        "name": "Reject recruiters",
        "field": "jobTitle",
        "operator": RuleOperator.REGEX,
        "value": r"recruit(er|ing|ment)|talent acquisition|headhunter",
        "action": RuleAction.REJECT,
        "action_value": "Job title indicates a recruiter",
        "priority": 12,
        "description": "Recruiters are out of scope for this offer.",
    },
    {
        "name": "Missing company",
        "field": "company",
        "operator": RuleOperator.IS_EMPTY,
        "action": RuleAction.SCORE,
        "points": -10,
        "priority": 200,
        "description": "A lead with no company is hard to personalise.",
    },
    {
        "name": "Has a website",
        "field": "website",
        "operator": RuleOperator.IS_NOT_EMPTY,
        "action": RuleAction.SCORE,
        "points": 5,
        "priority": 210,
        "description": "A company website is a buying signal for web/SaaS work.",
    },
]


def _titles_pattern(titles: list[str]) -> str:
    return "|".join(_escape(title) for title in titles if title)


def _escape(value: str) -> str:
    import re

    return re.escape(value.strip())


def derive_campaign_rules(campaign: Any, include_scoring: bool = True) -> list[RuleSpec]:
    """Build RuleSpecs from a campaign's targeting fields."""
    specs: list[RuleSpec] = []
    priority = 20

    for keyword in _as_list(getattr(campaign, "exclude_keywords", None)):
        specs.append(
            RuleSpec(
                name=f"Exclude '{keyword}'",
                field="headline",
                operator=RuleOperator.REGEX,
                value=rf"\b{_escape(keyword)}\b",
                action=RuleAction.REJECT,
                action_value=f"Matched campaign exclusion '{keyword}'",
                priority=priority,
                source="campaign",
            )
        )
        specs.append(
            RuleSpec(
                name=f"Exclude '{keyword}' (title)",
                field="jobTitle",
                operator=RuleOperator.REGEX,
                value=rf"\b{_escape(keyword)}\b",
                action=RuleAction.REJECT,
                action_value=f"Matched campaign exclusion '{keyword}'",
                priority=priority + 1,
                source="campaign",
            )
        )
        priority += 2

    if not include_scoring:
        return specs

    titles = _as_list(getattr(campaign, "target_titles", None))
    if titles:
        specs.append(
            RuleSpec(
                name=f"Target title ({', '.join(titles)})",
                field="jobTitle",
                operator=RuleOperator.REGEX,
                value=_titles_pattern(titles),
                action=RuleAction.SCORE,
                points=DERIVED_POINTS["title"],
                priority=100,
                source="campaign",
            )
        )

    for industry in _as_list(getattr(campaign, "target_industries", None)):
        specs.append(
            RuleSpec(
                name=f"Industry: {industry}",
                field="industry",
                operator=RuleOperator.CONTAINS,
                value=industry,
                action=RuleAction.SCORE,
                points=DERIVED_POINTS["industry"],
                priority=110,
                source="campaign",
            )
        )

    for location in _as_list(getattr(campaign, "target_locations", None)):
        specs.append(
            RuleSpec(
                name=f"Location: {location}",
                field="location",
                operator=RuleOperator.CONTAINS,
                value=location,
                action=RuleAction.SCORE,
                points=DERIVED_POINTS["location"],
                priority=120,
                source="campaign",
            )
        )

    size_min = getattr(campaign, "company_size_min", None)
    size_max = getattr(campaign, "company_size_max", None)
    if size_min is not None and size_max is not None:
        specs.append(
            RuleSpec(
                name=f"Company size {size_min}-{size_max}",
                field="companySize",
                operator=RuleOperator.BETWEEN,
                value=str(size_min),
                value2=str(size_max),
                action=RuleAction.SCORE,
                points=DERIVED_POINTS["company_size"],
                priority=130,
                source="campaign",
            )
        )

    for keyword in _as_list(getattr(campaign, "keywords", None)):
        specs.append(
            RuleSpec(
                name=f"Keyword: {keyword}",
                field="headline",
                operator=RuleOperator.CONTAINS,
                value=keyword,
                action=RuleAction.SCORE,
                points=DERIVED_POINTS["keyword"],
                priority=140,
                source="campaign",
            )
        )

    return specs


def _as_list(value: Any) -> list[str]:
    if not value:
        return []
    if isinstance(value, str):
        return [item.strip() for item in value.split(",") if item.strip()]
    return [str(item).strip() for item in value if str(item).strip()]
