"""
Salesforce schema cache + field metadata helpers.
"""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from g_gremlin import sfdc as sfdc_helpers


CACHE_ROOT = Path.home() / ".g_gremlin" / "cache" / "sfdc"
DEFAULT_SCHEMA_TTL_HOURS = 24.0


@dataclass
class FieldInfo:
    name: str
    type: str
    updateable: bool
    nillable: bool
    restricted_picklist: bool
    picklist_values: List[str]
    soap_type: Optional[str] = None


@dataclass
class SchemaInfo:
    object_name: str
    fields: Dict[str, FieldInfo]
    schema_hash: str
    cache_dir: Path
    metadata: Dict[str, Any]
    warnings: List[str]


def get_schema(
    object_name: str,
    *,
    identity: Dict[str, Any],
    org_alias: Optional[str] = None,
    max_age_hours: Optional[float] = DEFAULT_SCHEMA_TTL_HOURS,
) -> SchemaInfo:
    cache_dir, warnings = _resolve_cache_dir(identity)
    cache_dir.mkdir(parents=True, exist_ok=True)
    describe_path = cache_dir / f"{object_name}.json"
    meta_path = cache_dir / "meta.json"

    if describe_path.exists() and _is_cache_fresh(describe_path, max_age_hours):
        describe = json.loads(describe_path.read_text(encoding="utf-8"))
        from_cache = True
    else:
        describe = sfdc_helpers.describe_sobject(object_name, org_alias=org_alias)
        describe_path.write_text(json.dumps(describe, indent=2), encoding="utf-8")
        from_cache = False

    schema_hash = _sha256_json(describe)
    metadata = _load_meta(meta_path)
    metadata.update(
        {
            "org_id": identity.get("org_id"),
            "user_id": identity.get("user_id"),
            "api_version": identity.get("api_version"),
            "username": identity.get("username"),
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "cache_ttl_hours": max_age_hours,
            "objects": metadata.get("objects", {}),
        }
    )
    metadata["objects"][object_name] = {
        "schema_hash": schema_hash,
        "from_cache": from_cache,
        "path": str(describe_path),
    }
    metadata["capabilities_fingerprint"] = _compute_fingerprint(metadata["objects"])
    meta_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")

    fields = _parse_fields(describe)
    return SchemaInfo(
        object_name=object_name,
        fields=fields,
        schema_hash=schema_hash,
        cache_dir=cache_dir,
        metadata=metadata,
        warnings=warnings,
    )


def _parse_fields(describe_payload: Dict[str, Any]) -> Dict[str, FieldInfo]:
    fields: Dict[str, FieldInfo] = {}
    for item in describe_payload.get("result", {}).get("fields", []) or []:
        name = str(item.get("name") or "")
        if not name:
            continue
        pick_vals = [
            str(p.get("value"))
            for p in item.get("picklistValues", []) or []
            if p.get("active") and p.get("value") is not None
        ]
        fields[name] = FieldInfo(
            name=name,
            type=str(item.get("type") or ""),
            updateable=bool(item.get("updateable", False)),
            nillable=bool(item.get("nillable", True)),
            restricted_picklist=bool(item.get("restrictedPicklist", False)),
            picklist_values=pick_vals,
            soap_type=str(item.get("soapType") or "") or None,
        )
    return fields


def _resolve_cache_dir(identity: Dict[str, Any]) -> Tuple[Path, List[str]]:
    warnings: List[str] = []
    org_id = identity.get("org_id") or "unknown_org"
    user_id = identity.get("user_id") or "unknown_user"
    api_version = identity.get("api_version") or "unknown_api"
    if org_id == "unknown_org":
        warnings.append("org_id missing; schema cache scoped to unknown_org.")
    if user_id == "unknown_user":
        warnings.append("user_id missing; schema cache scoped to unknown_user.")
    if api_version == "unknown_api":
        warnings.append("api_version missing; schema cache scoped to unknown_api.")
    return CACHE_ROOT / str(org_id) / str(user_id) / str(api_version), warnings


def _is_cache_fresh(path: Path, max_age_hours: Optional[float]) -> bool:
    if max_age_hours is None:
        return True
    if max_age_hours <= 0:
        return False
    try:
        age_seconds = datetime.now(timezone.utc).timestamp() - path.stat().st_mtime
    except OSError:
        return False
    return age_seconds <= float(max_age_hours) * 3600.0


def _load_meta(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _compute_fingerprint(objects_meta: Dict[str, Any]) -> str:
    hashes = []
    for key in sorted(objects_meta.keys()):
        val = objects_meta.get(key) or {}
        if val.get("schema_hash"):
            hashes.append(f"{key}:{val.get('schema_hash')}")
    return hashlib.sha256("|".join(hashes).encode("utf-8")).hexdigest()


def _sha256_json(payload: Dict[str, Any]) -> str:
    raw = json.dumps(payload, sort_keys=True).encode("utf-8")
    return hashlib.sha256(raw).hexdigest()
