"""Tool: fetch_orchestration_templates -- fetch and save templates as JSON files."""

import json
import os
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from typing import Any, Dict, List, Optional

from ..sfdc.cli import resolve_org, run_soql, run_apex, SfCliError
from ..sfdc.apex_templates import build_export_apex

TOOL_NAME = "fetch_orchestration_templates"

TOOL_DEFINITION = {
    "name": TOOL_NAME,
    "description": (
        "Get, fetch, download, or export CloudSense orchestration process templates from a "
        "Salesforce org and save them as JSON files. "
        "Use this tool when the user says 'get templates', 'fetch templates', 'download templates', "
        "'export templates', 'pull templates', 'retrieve templates', or 'save templates'. "
        "When the user says 'get N templates' or 'get all templates', use THIS tool (not list). "
        "Templates are saved to deployment_data/orchestration_templates/<org>/<timestamp>/. "
        "If no template names are provided, fetches ALL templates from the org. "
        "Validates template names against the org before fetching and reports any that don't exist. "
        "Org is auto-resolved from session or workspace default if not specified."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "org_alias": {
                "type": "string",
                "description": (
                    "Salesforce org alias or partial hint (e.g. 'itxdevpro', 'devpro', 'sit p3'). "
                    "Partial names are fuzzy-matched against authenticated orgs. "
                    "OMIT this parameter if the user did not mention a specific org -- "
                    "the tool will automatically use the session org or workspace default."
                ),
            },
            "template_names": {
                "type": "array",
                "items": {"type": "string"},
                "description": (
                    "Specific template names to fetch, e.g. "
                    '["Internet CPE Terminate Flow (Subprocess)", "Callout to Inventory Reservation (Subprocess)"]. '
                    "If omitted, fetches ALL orchestration process templates from the org."
                ),
            },
            "max_workers": {
                "type": "integer",
                "description": "Number of parallel workers for concurrent fetching. Default: 5.",
                "default": 5,
            },
        },
        "required": [],
    },
}


def _sanitize_filename(name: str) -> str:
    sanitized = re.sub(r'[<>:"/\\|?*]', '_', name)
    sanitized = sanitized.replace(' ', '_')
    sanitized = sanitized.strip('._ ')
    return sanitized


def _fetch_single_template(template_name: str, org_alias: Optional[str], output_dir: str) -> Dict:
    """Fetch one template via anonymous Apex and save the JSON result to disk."""
    apex_code = build_export_apex(template_name)
    try:
        apex_result = run_apex(apex_code, org_alias=org_alias, timeout_seconds=60)
    except SfCliError as e:
        return {"name": template_name, "success": False, "error": str(e)}

    errors = apex_result.extract_debug_lines("###ERROR###")
    if errors:
        return {"name": template_name, "success": False, "error": "; ".join(errors)}

    json_str = apex_result.extract_chunks(prefix="###CHUNK_")
    if not json_str:
        return {
            "name": template_name,
            "success": False,
            "error": "No template data returned from Apex execution.",
        }

    sanitized = _sanitize_filename(template_name)
    filepath = os.path.join(output_dir, f"{sanitized}.json")
    try:
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(json_str)
        return {"name": template_name, "success": True, "file": filepath}
    except Exception as e:
        return {"name": template_name, "success": False, "error": f"Failed to save file: {e}"}


def _query_all_template_names(org_alias: Optional[str]) -> Optional[List[str]]:
    soql = "SELECT Name FROM CSPOFA__Orchestration_Process_Template__c ORDER BY Name ASC"
    try:
        result = run_soql(soql, org_alias=org_alias)
    except SfCliError as e:
        raise e
    return [r["Name"] for r in result.get("records", []) if r.get("Name")]


def _validate_template_names(names: List[str], org_alias: Optional[str]) -> Dict:
    """Check which of the provided names actually exist in the org."""
    quoted = ", ".join(f"'{n.replace(chr(39), '')}'" for n in names)
    soql = (
        "SELECT Name FROM CSPOFA__Orchestration_Process_Template__c "
        f"WHERE Name IN ({quoted})"
    )
    try:
        result = run_soql(soql, org_alias=org_alias)
    except SfCliError:
        return {"found": names, "not_found": [], "available": []}

    found_names = {r["Name"] for r in result.get("records", []) if r.get("Name")}
    not_found = [n for n in names if n not in found_names]

    available = []
    if not_found:
        all_names = _query_all_template_names(org_alias)
        if all_names:
            available = all_names

    return {
        "found": [n for n in names if n in found_names],
        "not_found": not_found,
        "available": available,
    }


async def execute(arguments: Dict[str, Any]) -> str:
    """Execute the fetch_orchestration_templates tool."""
    template_names = arguments.get("template_names")
    max_workers = arguments.get("max_workers", 5)

    org_resolution = resolve_org(arguments.get("org_alias") or None)

    if "error" in org_resolution:
        return json.dumps({
            "success": False,
            "error": org_resolution["error"],
            "candidates": org_resolution.get("candidates", []),
        }, indent=2)

    org_alias = org_resolution["org_alias"]
    org_source = org_resolution["org_source"]

    if not template_names:
        try:
            all_names = _query_all_template_names(org_alias)
        except SfCliError as e:
            return json.dumps({
                "success": False,
                "error": str(e),
                "org": org_alias,
                "org_source": org_source,
            }, indent=2)
        if not all_names:
            return json.dumps({
                "success": True,
                "message": "No orchestration process templates found in this org.",
                "org": org_alias,
                "org_source": org_source,
                "templates_fetched": 0,
            }, indent=2)
        template_names = all_names

    validation = _validate_template_names(template_names, org_alias)
    warnings = []
    if validation["not_found"]:
        not_found_msg = (
            f"Templates not found in org: {validation['not_found']}. "
            "They will be skipped."
        )
        if validation["available"]:
            not_found_msg += f" Available templates: {validation['available']}"
        warnings.append(not_found_msg)
        template_names = validation["found"]

    if not template_names:
        return json.dumps({
            "success": False,
            "error": "None of the specified template names exist in this org.",
            "org": org_alias,
            "org_source": org_source,
            "warnings": warnings,
        }, indent=2)

    org_folder = _sanitize_filename(org_alias)
    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')

    script_dir = os.path.dirname(os.path.abspath(__file__))
    base_dir = os.path.normpath(
        os.path.join(script_dir, '..', '..', '..', '..', '..', 'deployment_data', 'orchestration_templates')
    )
    output_dir = os.path.join(base_dir, org_folder, timestamp)

    try:
        os.makedirs(output_dir, exist_ok=True)
    except Exception as e:
        return json.dumps({
            "success": False,
            "error": f"Failed to create output directory '{output_dir}': {e}",
        }, indent=2)

    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_name = {
            executor.submit(_fetch_single_template, name, org_alias, output_dir): name
            for name in template_names
        }
        for future in as_completed(future_to_name):
            try:
                results.append(future.result())
            except Exception as exc:
                name = future_to_name[future]
                results.append({"name": name, "success": False, "error": str(exc)})

    succeeded = [r for r in results if r["success"]]
    failed = [r for r in results if not r["success"]]

    return json.dumps({
        "success": len(failed) == 0,
        "org": org_alias,
        "org_source": org_source,
        "output_directory": output_dir,
        "timestamp": timestamp,
        "summary": {
            "total_requested": len(results),
            "succeeded": len(succeeded),
            "failed": len(failed),
        },
        "succeeded": succeeded,
        "failed": failed,
        "warnings": warnings,
    }, indent=2)
