"""
Dataclass models for Scout checks.
"""

from __future__ import annotations

import hashlib
import json
import socket
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from g_gremlin.contracts import AgenticResult


SCOUT_SCHEMA_VERSION = "1"


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _coerce_int(value: Any) -> int:
    try:
        return int(value)
    except Exception:
        return 0


@dataclass
class ScoutParameter:
    name: str
    type: str = "string"  # string|number|boolean|list
    label: Optional[str] = None
    default: Any = None
    choices: Optional[List[Any]] = None
    required: bool = False

    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "type": self.type,
            "label": self.label or self.name,
            "default": self.default,
            "choices": self.choices,
            "required": self.required,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScoutParameter":
        return cls(
            name=data["name"],
            type=data.get("type", "string"),
            label=data.get("label") or data.get("name"),
            default=data.get("default"),
            choices=data.get("choices"),
            required=bool(data.get("required", False)),
        )


@dataclass
class ScoutDefinition:
    name: str
    display_name: str
    description: str
    category: str
    severity_default: str
    check_module: str
    check_class: str
    sobjects_read: List[str] = field(default_factory=list)
    sobjects_write: List[str] = field(default_factory=list)
    requires_credentials: List[str] = field(default_factory=list)
    supported_crms: List[str] = field(default_factory=list)
    field_overrides: Dict[str, Dict[str, Dict[str, str]]] = field(default_factory=dict)
    enabled: bool = True
    schema_version: str = SCOUT_SCHEMA_VERSION
    parameters: List[ScoutParameter] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "name": self.name,
            "display_name": self.display_name,
            "description": self.description,
            "category": self.category,
            "severity_default": self.severity_default,
            "check_module": self.check_module,
            "check_class": self.check_class,
            "sobjects_read": list(self.sobjects_read),
            "sobjects_write": list(self.sobjects_write),
            "requires_credentials": list(self.requires_credentials),
            "supported_crms": list(self.supported_crms),
            "field_overrides": self.field_overrides,
            "enabled": bool(self.enabled),
            "parameters": [p.to_dict() for p in self.parameters],
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScoutDefinition":
        params = [ScoutParameter.from_dict(p) for p in data.get("parameters", [])]
        return cls(
            name=data["name"],
            display_name=data.get("display_name", data.get("name", "")),
            description=data.get("description", ""),
            category=data.get("category", "general"),
            severity_default=data.get("severity_default", "medium"),
            check_module=data.get("check_module", ""),
            check_class=data.get("check_class", ""),
            sobjects_read=data.get("sobjects_read", []) or [],
            sobjects_write=data.get("sobjects_write", []) or [],
            requires_credentials=data.get("requires_credentials", []) or [],
            supported_crms=list(data.get("supported_crms", []) or []),
            field_overrides=data.get("field_overrides", {}) or {},
            enabled=bool(data.get("enabled", True)),
            schema_version=str(data.get("schema_version", SCOUT_SCHEMA_VERSION)),
            parameters=params,
        )


@dataclass
class EvidenceRow:
    record_id: str
    sobject: str
    fields: Dict[str, Any] = field(default_factory=dict)
    reason: str = ""
    reason_code: str = ""
    severity: Optional[str] = None
    related_ids: List[str] = field(default_factory=list)

    def to_dict(
        self,
        *,
        include_fields: bool = True,
        include_reason: bool = True,
    ) -> Dict[str, Any]:
        payload = {
            "record_id": self.record_id,
            "sobject": self.sobject,
            "reason_code": self.reason_code,
            "severity": self.severity,
            "related_ids": list(self.related_ids),
        }
        if include_reason:
            payload["reason"] = self.reason
        if include_fields:
            payload["fields"] = self.fields
        return payload

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EvidenceRow":
        return cls(
            record_id=str(data.get("record_id", "")),
            sobject=str(data.get("sobject", "")),
            fields=data.get("fields", {}) or {},
            reason=data.get("reason", ""),
            reason_code=data.get("reason_code", ""),
            severity=data.get("severity"),
            related_ids=list(data.get("related_ids", []) or []),
        )


@dataclass
class RecommendedAction:
    action_type: str
    label: str
    risk_level: str = "safe"
    details: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "action_type": self.action_type,
            "label": self.label,
            "risk_level": self.risk_level,
            "details": self.details,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "RecommendedAction":
        return cls(
            action_type=data.get("action_type", "manual_review"),
            label=data.get("label", ""),
            risk_level=data.get("risk_level", "safe"),
            details=data.get("details", {}) or {},
        )


@dataclass
class ScoutFinding:
    scout_name: str
    total_records_scanned: int = 0
    findings_count: int = 0
    evidence: List[EvidenceRow] = field(default_factory=list)
    recommended_actions: List[RecommendedAction] = field(default_factory=list)
    soql_queries: List[str] = field(default_factory=list)
    duration_ms: int = 0
    parameters_used: Dict[str, Any] = field(default_factory=dict)
    status: str = "ok"  # ok|findings|unsupported|error
    status_detail: Optional[str] = None
    display_name: Optional[str] = None
    description: Optional[str] = None
    category: Optional[str] = None
    severity: Optional[str] = None
    warnings: List[str] = field(default_factory=list)

    def to_dict(
        self,
        *,
        include_fields: bool = True,
        include_reason: bool = True,
    ) -> Dict[str, Any]:
        return {
            "$schema": "ScoutFinding/v1",
            "scout_name": self.scout_name,
            "display_name": self.display_name,
            "description": self.description,
            "category": self.category,
            "severity": self.severity,
            "total_records_scanned": self.total_records_scanned,
            "findings_count": self.findings_count,
            "evidence": [
                row.to_dict(include_fields=include_fields, include_reason=include_reason)
                for row in self.evidence
            ],
            "recommended_actions": [a.to_dict() for a in self.recommended_actions],
            "soql_queries": list(self.soql_queries),
            "duration_ms": self.duration_ms,
            "parameters_used": self.parameters_used,
            "status": self.status,
            "status_detail": self.status_detail,
            "warnings": list(self.warnings),
        }

    def to_agentic_result(self, *, include_fields: bool = True, include_reason: bool = True) -> AgenticResult:
        status = "success" if self.status in ("ok", "findings") else "error"
        result = {
            "$schema": "ScoutFinding/v1",
            "scout_name": self.scout_name,
            "display_name": self.display_name,
            "category": self.category,
            "severity": self.severity,
            "total_records_scanned": self.total_records_scanned,
            "findings_count": self.findings_count,
            "evidence": [
                row.to_dict(include_fields=include_fields, include_reason=include_reason)
                for row in self.evidence
            ],
            "recommended_actions": [a.to_dict() for a in self.recommended_actions],
            "soql_queries": list(self.soql_queries),
            "duration_ms": self.duration_ms,
            "parameters_used": self.parameters_used,
            "status": self.status,
            "status_detail": self.status_detail,
            "warnings": list(self.warnings),
        }
        return AgenticResult(
            command=f"g-gremlin scout run {self.scout_name}",
            status=status,
            dry_run=True,
            result=result,
        )

    def finding_hashes(self) -> List[str]:
        hashes: List[str] = []
        for row in self.evidence:
            base = f"{self.scout_name}:{row.record_id}:{row.reason_code}"
            digest = hashlib.sha256(base.encode("utf-8")).hexdigest()
            hashes.append(digest)
        return hashes

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScoutFinding":
        evidence = [EvidenceRow.from_dict(r) for r in data.get("evidence", [])]
        actions = [RecommendedAction.from_dict(a) for a in data.get("recommended_actions", [])]
        return cls(
            scout_name=data.get("scout_name", ""),
            total_records_scanned=_coerce_int(data.get("total_records_scanned", 0)),
            findings_count=_coerce_int(data.get("findings_count", 0)),
            evidence=evidence,
            recommended_actions=actions,
            soql_queries=list(data.get("soql_queries", []) or []),
            duration_ms=_coerce_int(data.get("duration_ms", 0)),
            parameters_used=data.get("parameters_used", {}) or {},
            status=data.get("status", "ok"),
            status_detail=data.get("status_detail"),
            display_name=data.get("display_name"),
            description=data.get("description"),
            category=data.get("category"),
            severity=data.get("severity"),
            warnings=list(data.get("warnings", []) or []),
        )


@dataclass
class ScoutScanResult:
    schema_version: str = SCOUT_SCHEMA_VERSION
    run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    started_at: str = field(default_factory=_utc_now_iso)
    ended_at: str = ""
    org_alias: Optional[str] = None
    hostname: str = field(default_factory=socket.gethostname)
    findings: List[ScoutFinding] = field(default_factory=list)
    errors: List[str] = field(default_factory=list)
    total_duration_ms: int = 0

    def to_dict(self, *, include_fields: bool = True, include_reason: bool = True) -> Dict[str, Any]:
        return {
            "$schema": "ScoutScanResult/v1",
            "schema_version": self.schema_version,
            "run_id": self.run_id,
            "started_at": self.started_at,
            "ended_at": self.ended_at,
            "org_alias": self.org_alias,
            "hostname": self.hostname,
            "findings": [
                f.to_dict(include_fields=include_fields, include_reason=include_reason)
                for f in self.findings
            ],
            "errors": list(self.errors),
            "total_duration_ms": self.total_duration_ms,
        }

    def to_agentic_result(self, *, include_fields: bool = True, include_reason: bool = True) -> AgenticResult:
        status = "success" if not self.errors else "partial"
        result = self.to_dict(include_fields=include_fields, include_reason=include_reason)
        return AgenticResult(
            command="g-gremlin scout scan",
            status=status,
            dry_run=True,
            result=result,
        )

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScoutScanResult":
        findings = [ScoutFinding.from_dict(f) for f in data.get("findings", [])]
        return cls(
            schema_version=str(data.get("schema_version", SCOUT_SCHEMA_VERSION)),
            run_id=data.get("run_id", str(uuid.uuid4())),
            started_at=data.get("started_at", _utc_now_iso()),
            ended_at=data.get("ended_at", ""),
            org_alias=data.get("org_alias"),
            hostname=data.get("hostname") or socket.gethostname(),
            findings=findings,
            errors=list(data.get("errors", []) or []),
            total_duration_ms=_coerce_int(data.get("total_duration_ms", 0)),
        )


@dataclass
class ScanConfig:
    scouts_enabled: List[str] = field(default_factory=list)
    top_n: int = 20
    per_scout_params: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    output_format: str = "table"
    org_alias: Optional[str] = None
    crm: str = "salesforce"
    hubspot_profile: Optional[str] = None
    dynamics_profile: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            "scouts_enabled": list(self.scouts_enabled),
            "top_n": self.top_n,
            "per_scout_params": self.per_scout_params,
            "output_format": self.output_format,
            "org_alias": self.org_alias,
            "crm": self.crm,
            "hubspot_profile": self.hubspot_profile,
            "dynamics_profile": self.dynamics_profile,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScanConfig":
        return cls(
            scouts_enabled=list(data.get("scouts_enabled", []) or []),
            top_n=_coerce_int(data.get("top_n", 20)),
            per_scout_params=data.get("per_scout_params", {}) or {},
            output_format=data.get("output_format", "table"),
            org_alias=data.get("org_alias"),
            crm=str(data.get("crm", "salesforce") or "salesforce"),
            hubspot_profile=data.get("hubspot_profile"),
            dynamics_profile=data.get("dynamics_profile"),
        )

    def merge(self, override: Dict[str, Any]) -> "ScanConfig":
        merged = self.to_dict()
        merged.update({k: v for k, v in override.items() if v is not None})
        return ScanConfig.from_dict(merged)


__all__ = [
    "ScoutParameter",
    "ScoutDefinition",
    "EvidenceRow",
    "RecommendedAction",
    "ScoutFinding",
    "ScoutScanResult",
    "ScanConfig",
    "SCOUT_SCHEMA_VERSION",
]
