"""
Thin wrappers around the Salesforce CLI (sf) for SOQL queries, Apex execution,
and org configuration. No REST API, no token management -- the CLI handles everything.
"""

import json
import os
import re
import subprocess
import tempfile
from typing import Any, Dict, List, Optional


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

    def __init__(self, message: str, stderr: str = "", stdout: str = ""):
        super().__init__(message)
        self.stderr = stderr
        self.stdout = stdout


def get_default_org() -> Optional[str]:
    """Return the default target-org alias from sf CLI config, or None."""
    try:
        result = subprocess.run(
            ["sf", "config", "get", "target-org", "--json"],
            capture_output=True,
            text=True,
            timeout=15,
        )
        if result.returncode == 0:
            data = json.loads(result.stdout)
            results = data.get("result", [])
            if isinstance(results, list) and results:
                return results[0].get("value")
            if isinstance(results, dict):
                return results.get("value")
    except Exception:
        pass
    return None


def run_soql(query: str, org_alias: Optional[str] = None) -> Dict[str, Any]:
    """
    Execute a SOQL query via `sf data query --json`.
    Returns the parsed JSON result dict with 'records', 'totalSize', etc.
    Raises SfCliError on failure.
    """
    cmd = ["sf", "data", "query", "--query", query, "--json"]
    if org_alias:
        cmd += ["--target-org", org_alias]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
    except subprocess.TimeoutExpired:
        raise SfCliError("SOQL query timed out after 120 seconds")

    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError:
        raise SfCliError(
            f"Failed to parse sf CLI output: {result.stdout[:500]}",
            stderr=result.stderr,
            stdout=result.stdout,
        )

    if result.returncode != 0:
        message = data.get("message", result.stderr or "Unknown error")
        raise SfCliError(f"SOQL query failed: {message}", stderr=result.stderr, stdout=result.stdout)

    return data.get("result", {})


def run_apex(
    apex_code: str,
    org_alias: Optional[str] = None,
    timeout_seconds: int = 120,
) -> "ApexResult":
    """
    Execute anonymous Apex via `sf apex run --file <tmpfile> --json`.
    Returns an ApexResult with parsed debug output.
    Raises SfCliError on failure.
    """
    tmp = tempfile.NamedTemporaryFile(
        mode="w", suffix=".apex", delete=False, encoding="utf-8"
    )
    try:
        tmp.write(apex_code)
        tmp.close()

        cmd = ["sf", "apex", "run", "--file", tmp.name, "--json"]
        if org_alias:
            cmd += ["--target-org", org_alias]

        try:
            result = subprocess.run(
                cmd, capture_output=True, text=True, timeout=timeout_seconds
            )
        except subprocess.TimeoutExpired:
            raise SfCliError(f"Apex execution timed out after {timeout_seconds} seconds")

        try:
            data = json.loads(result.stdout)
        except json.JSONDecodeError:
            raise SfCliError(
                f"Failed to parse sf CLI output: {result.stdout[:500]}",
                stderr=result.stderr,
                stdout=result.stdout,
            )

        if "result" not in data:
            error = data.get("message", result.stderr or "Unknown CLI error")
            raise SfCliError(f"Apex execution failed: {error}", stderr=result.stderr, stdout=result.stdout)

        apex_result = data["result"]

        if not apex_result.get("success", False):
            compile_problem = apex_result.get("compileProblem", "")
            exception_msg = apex_result.get("exceptionMessage", "")
            error = compile_problem or exception_msg or "Unknown Apex execution error"
            raise SfCliError(f"Apex execution failed: {error}", stderr=result.stderr, stdout=result.stdout)

        log_text = apex_result.get("logs", "")
        return ApexResult(success=True, log=log_text, raw=apex_result)
    finally:
        os.unlink(tmp.name)


class ApexResult:
    """Parsed result from an anonymous Apex execution."""

    def __init__(self, success: bool, log: str, raw: Dict[str, Any]):
        self.success = success
        self.log = log
        self.raw = raw

    def extract_chunks(self, prefix: str = "###CHUNK_") -> str:
        """
        Reassemble chunked output from System.debug log lines.
        Each USER_DEBUG line contains one chunk: ...USER_DEBUG|[N]|ERROR|###CHUNK_0###data
        Extracts data after the chunk marker on each line, sorts by index, and concatenates.
        """
        pattern = re.escape(prefix) + r"(\d+)###(.*)$"
        chunks = []
        for line in self.log.splitlines():
            if prefix not in line:
                continue
            m = re.search(pattern, line)
            if m:
                chunks.append((int(m.group(1)), m.group(2)))
        if not chunks:
            return ""
        chunks.sort(key=lambda c: c[0])
        return "".join(data for _, data in chunks)

    def extract_tagged(self, start_tag: str, end_tag: str) -> List[str]:
        """
        Extract all text blocks between start_tag and end_tag markers in the log.
        Returns a list of extracted strings.
        """
        pattern = re.escape(start_tag) + r"(.*?)" + re.escape(end_tag)
        return re.findall(pattern, self.log, re.DOTALL)

    def extract_debug_lines(self, marker: str) -> List[str]:
        """Extract data from USER_DEBUG log lines containing a specific marker.

        Filters out source-code echo lines (Execute Anonymous:) that happen
        to contain the marker as a string literal.
        """
        lines = []
        for line in self.log.splitlines():
            if "USER_DEBUG" in line and marker in line:
                idx = line.index(marker) + len(marker)
                lines.append(line[idx:])
        return lines
