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

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

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

from .safety import validate_sf_command, validate_soql_query

logger = logging.getLogger(__name__)

_sf_executable: str | None = None


def _resolve_sf_executable() -> str | None:
    """Find the sf executable, caching the result for the session.

    Lookup order:
    1. SF_CLI_PATH env var (explicit override, not advertised to end users)
    2. Standard PATH lookup (shutil.which)
    3. Windows-only: probe well-known install locations
    """
    global _sf_executable
    if _sf_executable is not None:
        return _sf_executable if _sf_executable != "" else None

    explicit = os.environ.get("SF_CLI_PATH")
    if explicit and (shutil.which(explicit) or Path(explicit).is_file()):
        _sf_executable = explicit
        return _sf_executable

    found = shutil.which("sf") or shutil.which("sf.cmd")
    if found:
        _sf_executable = found
        return _sf_executable

    if sys.platform == "win32":
        program_files = os.environ.get("ProgramFiles", r"C:\Program Files")
        program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")
        local_app_data = os.environ.get("LOCALAPPDATA", "")
        app_data = os.environ.get("APPDATA", "")
        user_profile = os.environ.get("USERPROFILE", "")

        candidates = [
            Path(program_files) / "sf" / "bin" / "sf.cmd",
            Path(program_files_x86) / "sf" / "bin" / "sf.cmd",
            Path(local_app_data) / "sf" / "bin" / "sf.cmd",
            Path(program_files) / "Salesforce CLI" / "bin" / "sf.cmd",
            Path(app_data) / "npm" / "sf.cmd",
            Path(r"C:\ProgramData\chocolatey\bin") / "sf.cmd",
            Path(user_profile) / "scoop" / "shims" / "sf.cmd",
        ]
        for candidate in candidates:
            try:
                if candidate.is_file():
                    _sf_executable = str(candidate)
                    return _sf_executable
            except OSError:
                continue

    _sf_executable = ""
    return None


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 = _resolve_sf_executable()
    if not sf_path:
        return {"installed": False, "version": None, "path": None}

    try:
        result = subprocess.run(
            [sf_path, "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.
    """
    sf_path = _resolve_sf_executable() or "sf"
    if isinstance(command, list):
        full_command = "sf " + " ".join(command)
        args = [sf_path] + command
    else:
        full_command = f"sf {command}"
        args = [sf_path] + 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 []
