"""
Safe SOQL builder for the Forecast template.

Structured filters only – no raw SOQL fragments – to avoid injection risk and
keep parity with Apps Script config payloads.
"""

from __future__ import annotations

import re
from typing import Any, Dict, List


class ForecastSoqlBuilder:
    """Builds SOQL for pulling Opportunities with safe, structured filters."""

    SAFE_FIELDS = [
        "Id",
        "Name",
        "AccountId",
        "Account.Name",
        "Amount",
        "CloseDate",
        "StageName",
        "Probability",
        "IsWon",
        "IsClosed",
        "ForecastCategoryName",
        "OwnerId",
        "Owner.Name",
        "RecordType.Name",
        "Type",
        "CreatedDate",
        "LastModifiedDate",
    ]

    def __init__(self) -> None:
        self._field_pattern = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_\.]*$")

    def build_opportunity_query(self, config: Dict[str, Any]) -> str:
        """
        Build SOQL for Opportunity pull.

        Notes:
        - No OFFSET usage; pagination must follow nextRecordsUrl/queryMore.
        - Filters are built from structured config only.
        """
        config = config or {}
        fields = list(self.SAFE_FIELDS)

        # Optional custom revenue field
        revenue_field = config.get("revenue_field") or "Amount"
        if revenue_field not in fields:
            fields.append(self._sanitize_field(revenue_field))

        # Optional custom credit owner field
        credit_field = config.get("credit_owner_field") or "OwnerId"
        if credit_field not in fields and credit_field != "OwnerId":
            fields.append(self._sanitize_field(credit_field))

        # Optional category source field
        cat_field = config.get("forecast_category_source") or "ForecastCategoryName"
        if cat_field not in fields:
            fields.append(self._sanitize_field(cat_field))

        where_clauses: List[str] = []

        excluded_stages = config.get("excluded_stages") or []
        if excluded_stages:
            escaped = [self._escape_string(s) for s in excluded_stages if s]
            if escaped:
                where_clauses.append(f"StageName NOT IN ({','.join(escaped)})")

        included_record_types = config.get("included_record_types") or []
        if included_record_types:
            escaped = [self._escape_string(rt) for rt in included_record_types if rt]
            if escaped:
                where_clauses.append(f"RecordType.Name IN ({','.join(escaped)})")

        window_days = config.get("close_date_window_days") or 0
        try:
            window_days = int(window_days)
        except Exception:
            window_days = 0

        if window_days and window_days > 0:
            where_clauses.append(f"CloseDate >= LAST_N_DAYS:{window_days}")
            where_clauses.append(f"CloseDate <= NEXT_N_DAYS:{window_days}")

        where_str = " AND ".join(where_clauses) if where_clauses else "1=1"
        field_list = ", ".join(fields)
        return f"SELECT {field_list} FROM Opportunity WHERE {where_str}"

    def _sanitize_field(self, field: str) -> str:
        if not self._field_pattern.match(field or ""):
            raise ValueError(f"Invalid field name: {field}")
        return field

    def _escape_string(self, value: str) -> str:
        escaped = str(value).replace("\\", "\\\\").replace("'", "\\'")
        return f"'{escaped}'"
