"""Safe wrapper around Salesforce CLI (sf).

Every command goes through the safety validator before execution.
"""

import json
import logging
import subprocess
import shutil
from pathlib import Path
from typing import Any

from .safety import validate_sf_command, validate_soql_query

logger = logging.getLogger(__name__)


class SfCliError(Exception):
    """Raised when an sf CLI command fails."""

    def __init__(self, message: str, command: str = "", exit_code: int = -1, stderr: str = ""):
        super().__init__(message)
        self.command = command
        self.exit_code = exit_code
        self.stderr = stderr


def check_sf_installed() -> dict[str, Any]:
    """Check if Salesforce CLI is installed and return version info."""
    sf_path = shutil.which("sf")
    if not sf_path:
        return {"installed": False, "version": None, "path": None}

    try:
        result = subprocess.run(
            ["sf", "version", "--json"],
            capture_output=True, text=True, timeout=15,
        )
        if result.returncode == 0:
            try:
                data = json.loads(result.stdout)
                version = data.get("cliVersion", result.stdout.strip())
            except json.JSONDecodeError:
                version = result.stdout.strip()
            return {"installed": True, "version": version, "path": sf_path}
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass

    return {"installed": True, "version": "unknown", "path": sf_path}


def run_sf(
    command: str | list[str],
    *,
    cwd: Path | str | None = None,
    timeout: int = 120,
    json_output: bool = True,
) -> dict[str, Any]:
    """Run an sf CLI command safely.

    The command string should NOT include 'sf' prefix.
    """
    if isinstance(command, list):
        full_command = "sf " + " ".join(command)
        args = ["sf"] + command
    else:
        full_command = f"sf {command}"
        args = ["sf"] + command.split()

    validate_sf_command(full_command)

    if json_output and "--json" not in args:
        args.append("--json")

    logger.info("Running: %s (cwd=%s)", " ".join(args), cwd or ".")

    try:
        result = subprocess.run(
            args, capture_output=True, text=True,
            cwd=str(cwd) if cwd else None, timeout=timeout,
        )
    except subprocess.TimeoutExpired:
        raise SfCliError(
            f"Command timed out after {timeout}s: {full_command}",
            command=full_command, exit_code=-1, stderr="TimeoutExpired",
        )
    except FileNotFoundError:
        raise SfCliError(
            "Salesforce CLI (sf) not found on PATH.",
            command=full_command, exit_code=-1, stderr="FileNotFoundError",
        )

    if result.returncode != 0:
        stderr = result.stderr.strip() or result.stdout.strip()
        raise SfCliError(
            f"sf command failed (exit {result.returncode}): {stderr}",
            command=full_command, exit_code=result.returncode, stderr=stderr,
        )

    if json_output:
        try:
            return json.loads(result.stdout)
        except json.JSONDecodeError:
            return {"stdout": result.stdout.strip()}

    return {"stdout": result.stdout.strip()}


def list_orgs(cwd: Path | str | None = None) -> list[dict[str, Any]]:
    """List all authorized Salesforce orgs."""
    data = run_sf("org list", cwd=cwd)
    result = data.get("result", {})
    orgs = []
    for category in ("nonScratchOrgs", "scratchOrgs"):
        orgs.extend(result.get(category, []))
    return orgs


def display_org(alias: str, cwd: Path | str | None = None) -> dict[str, Any]:
    """Get detailed info about a specific org."""
    return run_sf(f"org display --target-org {alias}", cwd=cwd)


def lma_query(
    query: str,
    lma_org: str,
    cwd: Path | str | None = None,
    timeout: int = 120,
) -> list[dict[str, Any]]:
    """Run a read-only SOQL query against the LMA org."""
    validate_soql_query(query, target_org=lma_org, lma_org=lma_org)
    data = run_sf(
        ["data", "query", "--query", query, "--target-org", lma_org],
        cwd=cwd, timeout=timeout,
    )
    result = data.get("result", {})
    records = result.get("records") or []
    return records if isinstance(records, list) else []


def sosl_search(
    search_term: str,
    returning: str,
    target_org: str,
    cwd: Path | str | None = None,
    timeout: int = 60,
) -> list[dict[str, Any]]:
    """Run a SOSL search via sf data search."""
    sosl_query = f"FIND {{{search_term}}} IN NAME FIELDS RETURNING {returning}"
    data = run_sf(
        ["data", "search", "--query", sosl_query, "--target-org", target_org],
        cwd=cwd, timeout=timeout,
    )
    result = data.get("result", {})
    records = result.get("searchRecords") or []
    return records if isinstance(records, list) else []
