"""
Salesforce smoke assertions via sf data query.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List

from g_gremlin import sfdc as sfdc_helpers


@dataclass
class SmokeAssertion:
    type: str
    params: Dict[str, Any]


@dataclass
class SmokeResult:
    assertion: SmokeAssertion
    passed: bool
    detail: str = ""

    @property
    def skipped(self) -> bool:
        return self.detail.startswith("skipped:")

    def to_dict(self) -> Dict[str, Any]:
        return {
            "type": self.assertion.type,
            "params": self.assertion.params,
            "passed": self.passed,
            "detail": self.detail,
        }


@dataclass
class SmokeReport:
    results: List[SmokeResult]

    @property
    def all_passed(self) -> bool:
        return all(r.passed or r.skipped for r in self.results)

    @property
    def summary(self) -> Dict[str, int]:
        total = len(self.results)
        passed = len([r for r in self.results if r.passed])
        failed = len([r for r in self.results if (not r.passed) and (not r.skipped)])
        skipped = len([r for r in self.results if r.skipped])
        return {"total": total, "passed": passed, "failed": failed, "skipped": skipped}

    def to_dict(self) -> Dict[str, Any]:
        return {"summary": self.summary, "results": [r.to_dict() for r in self.results]}


def load_smoke_assertions(pack_yaml_dict: Dict[str, Any]) -> List[SmokeAssertion]:
    items = pack_yaml_dict.get("smoke") or []
    assertions: List[SmokeAssertion] = []
    if isinstance(items, list):
        for item in items:
            if not isinstance(item, dict):
                continue
            atype = item.get("type") or item.get("assertion")
            params = item.get("params") or {k: v for k, v in item.items() if k not in {"type", "assertion"}}
            if atype:
                assertions.append(SmokeAssertion(type=str(atype), params=params))
    return assertions


def run_smoke(
    assertions: List[SmokeAssertion],
    *,
    org_alias: str | None = None,
) -> SmokeReport:
    results: List[SmokeResult] = []
    for assertion in assertions:
        atype = assertion.type
        params = assertion.params or {}

        if atype == "field_equals":
            result = _assert_field_equals(params, org_alias=org_alias)
            results.append(result)
            continue

        if atype == "soql_count":
            result = _assert_soql_count(params, org_alias=org_alias)
            results.append(result)
            continue

        if atype == "query_returns":
            result = _assert_query_returns(params, org_alias=org_alias)
            results.append(result)
            continue

        raise ValueError(f"Unknown smoke assertion type: {atype}")

    return SmokeReport(results=results)


def _assert_field_equals(params: Dict[str, Any], *, org_alias: str | None) -> SmokeResult:
    sobject = str(params.get("object") or params.get("sobject") or "")
    record_id = params.get("id") or params.get("record_id")
    where = params.get("where")
    field = str(params.get("field") or "")
    expected = params.get("value")

    if not sobject or not field:
        return SmokeResult(
            assertion=SmokeAssertion(type="field_equals", params=params),
            passed=False,
            detail="missing object or field",
        )
    try:
        sfdc_helpers.ensure_valid_identifier(sobject, "object name")
        sfdc_helpers.ensure_valid_identifier(field, "field")
    except Exception as exc:
        return SmokeResult(
            assertion=SmokeAssertion(type="field_equals", params=params),
            passed=False,
            detail=str(exc),
        )

    if record_id:
        soql = f"SELECT Id,{field} FROM {sobject} WHERE Id = '{sfdc_helpers.escape_soql_value(str(record_id))}'"
    elif where:
        if ";" in str(where):
            return SmokeResult(
                assertion=SmokeAssertion(type="field_equals", params=params),
                passed=False,
                detail="WHERE clause contains ';' which is not allowed.",
            )
        soql = f"SELECT Id,{field} FROM {sobject} WHERE {where} LIMIT 1"
    else:
        return SmokeResult(
            assertion=SmokeAssertion(type="field_equals", params=params),
            passed=False,
            detail="missing id or where",
        )

    rows = sfdc_helpers.run_soql(soql, org_alias=org_alias)
    if not rows:
        return SmokeResult(
            assertion=SmokeAssertion(type="field_equals", params=params),
            passed=False,
            detail="record not found",
        )
    value = rows[0].get(field)
    passed = str(value or "") == str(expected or "")
    detail = "" if passed else f"{field}='{value}' != '{expected}'"
    return SmokeResult(assertion=SmokeAssertion(type="field_equals", params=params), passed=passed, detail=detail)


def _assert_soql_count(params: Dict[str, Any], *, org_alias: str | None) -> SmokeResult:
    soql = str(params.get("soql") or "")
    if not soql:
        return SmokeResult(
            assertion=SmokeAssertion(type="soql_count", params=params),
            passed=False,
            detail="missing soql",
        )
    if ";" in soql:
        return SmokeResult(
            assertion=SmokeAssertion(type="soql_count", params=params),
            passed=False,
            detail="soql contains ';' which is not allowed.",
        )
    rows = sfdc_helpers.run_soql(soql, org_alias=org_alias)
    count = _extract_count(rows)
    expected = params.get("equals")
    min_val = params.get("min")
    max_val = params.get("max")
    passed = True
    detail = ""
    if expected is not None:
        passed = count == int(expected)
        detail = "" if passed else f"count {count} != {expected}"
    if min_val is not None and passed:
        passed = count >= int(min_val)
        detail = "" if passed else f"count {count} < {min_val}"
    if max_val is not None and passed:
        passed = count <= int(max_val)
        detail = "" if passed else f"count {count} > {max_val}"
    return SmokeResult(assertion=SmokeAssertion(type="soql_count", params=params), passed=passed, detail=detail)


def _assert_query_returns(params: Dict[str, Any], *, org_alias: str | None) -> SmokeResult:
    soql = str(params.get("soql") or "")
    min_rows = int(params.get("min_rows") or 1)
    if not soql:
        return SmokeResult(
            assertion=SmokeAssertion(type="query_returns", params=params),
            passed=False,
            detail="missing soql",
        )
    if ";" in soql:
        return SmokeResult(
            assertion=SmokeAssertion(type="query_returns", params=params),
            passed=False,
            detail="soql contains ';' which is not allowed.",
        )
    rows = sfdc_helpers.run_soql(soql, org_alias=org_alias)
    passed = len(rows) >= min_rows
    detail = "" if passed else f"rows {len(rows)} < {min_rows}"
    return SmokeResult(assertion=SmokeAssertion(type="query_returns", params=params), passed=passed, detail=detail)


def _extract_count(rows: List[Dict[str, Any]]) -> int:
    if not rows:
        return 0
    if "expr0" in rows[0]:
        try:
            return int(rows[0].get("expr0") or 0)
        except ValueError:
            return 0
    if "count" in rows[0]:
        try:
            return int(rows[0].get("count") or 0)
        except ValueError:
            return 0
    # fallback to row count
    return len(rows)

