"""get_customer_json_settings -- Exports CS custom setting values.

CloudSense packages use Salesforce Custom Settings (hierarchy and list)
to store configuration. These values are critical for upgrade assessment
because:
- Feature flags may change behavior between versions
- Default values may shift in the target version
- Customer-modified settings may conflict with new defaults

This tool:
1. Scans retrieved CS object metadata for <customSettingsType> in object-meta.xml
2. For each custom setting, calls sf sobject describe to get queryable fields
3. Builds SOQL queries and exports values via sf data query
4. Writes per-setting JSON files under cs-settings-export/
"""

import json
import logging
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Any

from ..utils import sf_cli, state, paths

logger = logging.getLogger(__name__)

TOOL_DEFINITION = {
    "name": "get_cs_settings",
    "description": (
        "Discovers CloudSense custom setting objects from retrieved metadata, "
        "describes their fields, queries the org to export all setting values. "
        "Writes per-setting JSON files. Must be called AFTER get_managed_objects."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the assessment working directory."
                ),
            },
            "org_alias": {
                "type": "string",
                "description": "Salesforce org alias to query.",
            },
            "customer_name": {
                "type": "string",
                "description": (
                    "Customer name. Used to locate the SFDX project directory."
                ),
            },
        },
        "required": ["working_directory", "org_alias", "customer_name"],
    },
}

SF_METADATA_NS = "http://soap.sforce.com/2006/04/metadata"

NON_QUERYABLE_FIELDS = {
    "Id", "IsDeleted", "CreatedById", "CreatedDate",
    "LastModifiedById", "LastModifiedDate", "SystemModstamp",
    "SetupOwnerId",
}


def _find_custom_settings(objects_dir: Path) -> list[dict[str, str]]:
    """Scan retrieved object directories for custom settings.

    Reads the object-meta.xml to check for <customSettingsType>.
    Returns list of {name, type, label} dicts.
    """
    settings = []

    if not objects_dir.exists():
        return settings

    for obj_dir in sorted(objects_dir.iterdir()):
        if not obj_dir.is_dir():
            continue

        meta_file = obj_dir / f"{obj_dir.name}.object-meta.xml"
        if not meta_file.exists():
            continue

        try:
            tree = ET.parse(meta_file)
            root = tree.getroot()

            cs_type_elem = root.find(
                f"{{{SF_METADATA_NS}}}customSettingsType"
            )
            if cs_type_elem is None:
                cs_type_elem = root.find("customSettingsType")

            if cs_type_elem is not None and cs_type_elem.text:
                label_elem = (
                    root.find(f"{{{SF_METADATA_NS}}}label")
                    or root.find("label")
                )
                settings.append({
                    "name": obj_dir.name,
                    "type": cs_type_elem.text,
                    "label": label_elem.text if label_elem is not None else obj_dir.name,
                })
        except ET.ParseError:
            logger.warning("Failed to parse %s", meta_file)

    return settings


def _get_queryable_fields(
    sobject_name: str, org_alias: str, cwd: Path | None,
) -> list[str]:
    """Get queryable custom field API names from sf sobject describe."""
    try:
        desc = sf_cli.describe_sobject(sobject_name, org_alias, cwd=cwd)
    except sf_cli.SfCliError:
        return []

    fields = desc.get("fields") or []
    queryable = []
    for f in fields:
        name = f.get("name", "")
        if not f.get("deprecatedAndHidden", False) and name not in NON_QUERYABLE_FIELDS:
            queryable.append(name)

    return sorted(queryable)


def _export_setting_values(
    sobject_name: str,
    fields: list[str],
    org_alias: str,
    cwd: Path | None,
) -> tuple[list[dict[str, Any]], str | None]:
    """Query all records for a custom setting. Returns (records, error)."""
    if not fields:
        return [], "no queryable fields"

    field_csv = ", ".join(fields)
    query = f"SELECT {field_csv} FROM {sobject_name}"

    try:
        records = sf_cli.data_query(query, org_alias, cwd=cwd)
        for r in records:
            r.pop("attributes", None)
        return records, None
    except sf_cli.SfCliError as e:
        return [], str(e)


async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_customer_json_settings tool."""
    working_dir = Path(arguments["working_directory"])
    org_alias = arguments["org_alias"]
    customer = arguments["customer_name"]

    report: list[str] = []
    warnings: list[str] = []

    # ── 0. Prerequisites ─────────────────────────────────────────
    if not state.is_step_completed(working_dir, "get_managed_objects"):
        return (
            "## BLOCKER: get_managed_objects not completed\n\n"
            "You must call get_managed_objects first so that "
            "CS object metadata is available for scanning.\n"
        )

    # ── 1. Resume check ──────────────────────────────────────────
    if state.is_step_completed(working_dir, "get_json_settings"):
        current_state = state.load_state(working_dir)
        info = (current_state or {}).get("json_settings_exported", {})
        return (
            "## Step already completed\n\n"
            f"Custom settings export was already run. "
            f"Found {info.get('settings_found', '?')} custom settings, "
            f"exported {info.get('records_total', '?')} records.\n\n"
            "**Next step:** call `get_object_customizations` "
            "or proceed to repository mapping."
        )

    state.mark_step_started(working_dir, "get_json_settings")

    project_dir = working_dir / customer
    objects_dir = project_dir / "force-app" / "main" / "default" / "objects"

    report.append("## Custom Settings Export\n")

    # ── 2. Find custom settings from retrieved metadata ──────────
    settings = _find_custom_settings(objects_dir)
    report.append(
        f"Found **{len(settings)}** custom settings in retrieved metadata.\n"
    )

    if not settings:
        state.mark_step_completed(working_dir, "get_json_settings")
        state.update_state(
            working_dir,
            json_settings_exported={
                "settings_found": 0,
                "records_total": 0,
            },
        )
        return "\n".join(report) + (
            "\nNo custom settings found among retrieved CS objects. "
            "This is unusual but not necessarily an error -- "
            "the org may use custom metadata types instead.\n\n"
            "## STATUS: READY\n"
        )

    report.append("| Setting | Type | Label |")
    report.append("|---------|------|-------|")
    for s in settings:
        report.append(f"| {s['name']} | {s['type']} | {s['label']} |")

    # ── 3. Describe and export each setting ───────────────────────
    export_dir = paths.ensure_dir(project_dir / "cs-settings-export")
    report.append(f"\n### Export\n")

    exported = []
    failed = []
    total_records = 0
    total_fields = 0

    for setting in settings:
        name = setting["name"]

        fields = _get_queryable_fields(name, org_alias, cwd=project_dir)
        if not fields:
            report.append(f"  - **{name}**: skipped (no queryable fields)")
            failed.append({"name": name, "reason": "no queryable fields"})
            continue

        records, error = _export_setting_values(
            name, fields, org_alias, cwd=project_dir,
        )

        if error:
            report.append(f"  - **{name}**: FAILED -- {error}")
            failed.append({"name": name, "reason": error})
            continue

        output_file = export_dir / f"{name}.json"
        output_file.write_text(json.dumps({
            "setting_name": name,
            "setting_type": setting["type"],
            "field_count": len(fields),
            "record_count": len(records),
            "fields": fields,
            "records": records,
        }, indent=2))

        total_records += len(records)
        total_fields += len(fields)
        exported.append({
            "name": name,
            "type": setting["type"],
            "fields": len(fields),
            "records": len(records),
        })
        report.append(
            f"  - **{name}** ({setting['type']}): "
            f"{len(fields)} fields, {len(records)} records"
        )

    # ── 4. Write summary file ─────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")
    summary_file = output_dir / "cs-settings-summary.json"
    summary_file.write_text(json.dumps({
        "total_settings": len(settings),
        "exported": exported,
        "failed": failed,
        "total_records": total_records,
        "total_fields": total_fields,
    }, indent=2))

    report.append(f"\n### Output\n")
    report.append(f"- Setting values: `{export_dir}/`")
    report.append(f"- Summary: `{summary_file}`")

    # ── 5. Update state ───────────────────────────────────────────
    state.mark_step_completed(working_dir, "get_json_settings")
    state.update_state(
        working_dir,
        json_settings_exported={
            "settings_found": len(settings),
            "exported": len(exported),
            "failed": len(failed),
            "records_total": total_records,
            "fields_total": total_fields,
        },
    )

    # ── 6. Summary ────────────────────────────────────────────────
    report.append("\n---\n")
    if failed:
        report.append("## STATUS: PARTIAL\n")
        warnings.append(
            f"{len(failed)} setting(s) could not be exported. "
            "These may require manual review."
        )
    else:
        report.append("## STATUS: READY\n")

    report.append(
        f"Exported {total_records} records across {len(exported)} "
        f"custom settings ({total_fields} total fields).\n\n"
        "**Next step:** call `get_object_customizations` "
        "or proceed to repository mapping."
    )

    if warnings:
        report.append("\n## Warnings\n")
        for w in warnings:
            report.append(f"- {w}")

    return "\n".join(report)
