"""get_customer_object_customizations -- Finds customer-added fields on CS objects.

When customers use CloudSense, they often add their own custom fields,
validation rules, record types, etc. to CS managed package objects.
For example, adding 'nbnco__Custom_Field__c' to 'csord__Order__c'.

These customizations are critical for upgrade assessment because:
- They might reference deprecated APIs or fields
- New package fields could conflict with customer field names
- Validation rules may fail if underlying CS fields change

This tool post-processes the metadata retrieved by get_managed_package_objects
to identify all customer-added components on CS objects by comparing
namespaces: if a field's namespace differs from its parent object's
namespace, it's customer-added.
"""

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

from ..utils import state, paths

logger = logging.getLogger(__name__)

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

TOOL_DEFINITION = {
    "name": "get_object_customizations",
    "description": (
        "Post-processes retrieved CS managed package objects to find "
        "customer-added fields, validation rules, and record types. "
        "Compares namespaces: if a field on a CS object has a different "
        "namespace (or no namespace), it's a customer customization. "
        "Must be called AFTER get_managed_objects."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the assessment working directory."
                ),
            },
            "customer_name": {
                "type": "string",
                "description": (
                    "Customer name. Used to locate the SFDX project directory."
                ),
            },
        },
        "required": ["working_directory", "customer_name"],
    },
}

COMPONENT_DIRS = {
    "fields": ".field-meta.xml",
    "validationRules": ".validationRule-meta.xml",
    "recordTypes": ".recordType-meta.xml",
    "webLinks": ".webLink-meta.xml",
    "listViews": ".listView-meta.xml",
    "compactLayouts": ".compactLayout-meta.xml",
    "fieldSets": ".fieldSet-meta.xml",
}


def _get_object_namespace(obj_name: str) -> str:
    """Extract namespace from sObject API name."""
    if "__" in obj_name:
        return obj_name.split("__")[0]
    return ""


def _get_field_namespace(field_name: str) -> str:
    """Extract namespace from a field API name.

    'csord__FieldName__c' -> 'csord'
    'CustomField__c' -> ''  (no namespace = customer-added)
    'Name' -> '' (standard field)
    """
    if "__" in field_name:
        return field_name.split("__")[0]
    return ""


def _is_custom_component(component_name: str) -> bool:
    """Check if component name indicates a custom component (ends in __c)."""
    return component_name.endswith("__c")


def _extract_field_info(meta_file: Path) -> dict[str, str]:
    """Read field metadata XML and extract key properties."""
    info: dict[str, str] = {"file": meta_file.name}

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

        for tag_name in ("fullName", "label", "type", "required",
                         "description", "formula", "referenceTo"):
            elem = (
                root.find(f"{{{SF_METADATA_NS}}}{tag_name}")
                or root.find(tag_name)
            )
            if elem is not None and elem.text:
                info[tag_name] = elem.text
    except ET.ParseError:
        info["parse_error"] = "true"

    return info


def _scan_object_customizations(
    obj_dir: Path,
    obj_namespace: str,
) -> dict[str, Any]:
    """Scan a single object directory for customer-added components."""
    customizations: dict[str, list[dict[str, Any]]] = {}

    for comp_type, suffix in COMPONENT_DIRS.items():
        comp_dir = obj_dir / comp_type
        if not comp_dir.exists():
            continue

        for meta_file in sorted(comp_dir.iterdir()):
            if not meta_file.name.endswith(suffix):
                continue

            comp_name = meta_file.name.replace(suffix, "")
            comp_ns = _get_field_namespace(comp_name)

            is_customer_added = False
            if comp_type == "fields":
                if not _is_custom_component(comp_name):
                    continue
                if comp_ns != obj_namespace:
                    is_customer_added = True
            else:
                if comp_ns != obj_namespace:
                    is_customer_added = True

            if is_customer_added:
                info = _extract_field_info(meta_file)
                info["component_name"] = comp_name
                info["component_type"] = comp_type
                info["namespace"] = comp_ns or "(none)"
                customizations.setdefault(comp_type, []).append(info)

    return customizations


def _get_cs_namespaces_from_state(working_dir: Path) -> set[str]:
    """Read CS package namespaces from the state file."""
    current_state = state.load_state(working_dir)
    if not current_state:
        return set()

    packages_info = current_state.get("packages_discovered", {})
    packages = packages_info.get("packages", [])

    return {
        p["namespace"]
        for p in packages
        if p.get("is_cloudsense") and p.get("namespace")
    }


async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_customer_object_customizations tool."""
    working_dir = Path(arguments["working_directory"])
    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.\n"
        )

    # ── 1. Resume check ──────────────────────────────────────────
    if state.is_step_completed(working_dir, "get_object_customizations"):
        current_state = state.load_state(working_dir)
        info = (current_state or {}).get("object_customizations", {})
        return (
            "## Step already completed\n\n"
            f"Object customizations analysis already run. "
            f"Found {info.get('total_customizations', '?')} customer "
            f"customizations across {info.get('objects_with_customizations', '?')} "
            f"objects.\n\n"
            "**Next step:** proceed to repository mapping "
            "(`get_repo_mapping`)."
        )

    state.mark_step_started(working_dir, "get_object_customizations")

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

    report.append("## Customer Object Customizations\n")

    if not objects_dir.exists():
        state.mark_step_failed(
            working_dir, "get_object_customizations",
            "Objects directory not found -- metadata not retrieved"
        )
        return (
            "## FAILED: Objects directory not found\n\n"
            f"Expected at: `{objects_dir}`\n"
            "Run get_customer_metadata and get_managed_objects first.\n"
        )

    # ── 2. Get CS namespaces ──────────────────────────────────────
    cs_namespaces = _get_cs_namespaces_from_state(working_dir)
    if not cs_namespaces:
        state.mark_step_failed(
            working_dir, "get_object_customizations",
            "No CS namespaces in state"
        )
        return (
            "## BLOCKER: No CloudSense namespaces in state\n\n"
            "Run get_installed_packages first.\n"
        )

    # ── 3. Scan each CS object for customer customizations ────────
    all_customizations: dict[str, dict[str, Any]] = {}
    total_custom_fields = 0
    total_custom_rules = 0
    total_custom_other = 0
    objects_scanned = 0

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

        obj_name = obj_dir.name
        obj_ns = _get_object_namespace(obj_name)

        if obj_ns not in cs_namespaces:
            continue

        objects_scanned += 1
        customizations = _scan_object_customizations(obj_dir, obj_ns)

        if customizations:
            field_count = len(customizations.get("fields", []))
            rule_count = len(customizations.get("validationRules", []))
            other_count = sum(
                len(v) for k, v in customizations.items()
                if k not in ("fields", "validationRules")
            )

            all_customizations[obj_name] = {
                "object_namespace": obj_ns,
                "customizations": customizations,
                "counts": {
                    "fields": field_count,
                    "validationRules": rule_count,
                    "other": other_count,
                    "total": field_count + rule_count + other_count,
                },
            }

            total_custom_fields += field_count
            total_custom_rules += rule_count
            total_custom_other += other_count

    total_all = total_custom_fields + total_custom_rules + total_custom_other

    report.append(f"Scanned **{objects_scanned}** CS objects.\n")
    report.append(f"### Summary\n")
    report.append(f"- **{len(all_customizations)}** objects have customer customizations")
    report.append(f"- **{total_custom_fields}** customer-added fields")
    report.append(f"- **{total_custom_rules}** customer-added validation rules")
    report.append(f"- **{total_custom_other}** other customizations "
                  f"(record types, list views, etc.)")
    report.append(f"- **{total_all}** total customizations\n")

    # ── 4. Detailed table ─────────────────────────────────────────
    if all_customizations:
        report.append("### Objects with Customizations\n")
        report.append("| Object | Fields | Rules | Other | Total |")
        report.append("|--------|--------|-------|-------|-------|")
        for obj_name in sorted(all_customizations):
            c = all_customizations[obj_name]["counts"]
            report.append(
                f"| {obj_name} | {c['fields']} | "
                f"{c['validationRules']} | {c['other']} | {c['total']} |"
            )

        top_objects = sorted(
            all_customizations.items(),
            key=lambda x: x[1]["counts"]["total"],
            reverse=True,
        )[:10]

        if top_objects:
            report.append("\n### Top Customized Objects (detail)\n")
            for obj_name, data in top_objects:
                report.append(f"#### {obj_name}\n")
                for comp_type, items in data["customizations"].items():
                    if items:
                        report.append(f"**{comp_type}** ({len(items)}):")
                        for item in items[:20]:
                            name = item.get("component_name", "?")
                            ns = item.get("namespace", "?")
                            ftype = item.get("type", "")
                            detail = f" ({ftype})" if ftype else ""
                            report.append(f"  - `{name}` [ns: {ns}]{detail}")
                        if len(items) > 20:
                            report.append(
                                f"  - ... and {len(items) - 20} more"
                            )
                report.append("")

    # ── 5. Write output file ──────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")
    output_file = output_dir / "customer-object-customizations.json"
    output_file.write_text(json.dumps({
        "objects_scanned": objects_scanned,
        "objects_with_customizations": len(all_customizations),
        "totals": {
            "fields": total_custom_fields,
            "validationRules": total_custom_rules,
            "other": total_custom_other,
            "total": total_all,
        },
        "objects": {
            obj_name: {
                "namespace": data["object_namespace"],
                "counts": data["counts"],
                "customizations": {
                    comp_type: [
                        {
                            "name": item.get("component_name"),
                            "namespace": item.get("namespace"),
                            "type": item.get("type"),
                            "label": item.get("label"),
                            "referenceTo": item.get("referenceTo"),
                        }
                        for item in items
                    ]
                    for comp_type, items in data["customizations"].items()
                },
            }
            for obj_name, data in sorted(all_customizations.items())
        },
    }, indent=2))

    report.append(f"\n### Output\n")
    report.append(f"- Customizations report: `{output_file}`")

    # ── 6. Update state ───────────────────────────────────────────
    state.mark_step_completed(working_dir, "get_object_customizations")
    state.update_state(
        working_dir,
        object_customizations={
            "objects_scanned": objects_scanned,
            "objects_with_customizations": len(all_customizations),
            "total_customizations": total_all,
            "fields": total_custom_fields,
            "validation_rules": total_custom_rules,
            "other": total_custom_other,
        },
    )

    # ── 7. Summary ────────────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")

    if total_all == 0:
        report.append(
            "No customer customizations found on CS objects. "
            "This means the customer uses CS objects as-is without adding "
            "their own fields or rules.\n\n"
        )
    else:
        report.append(
            f"Found **{total_all}** customer customizations across "
            f"**{len(all_customizations)}** CS objects. "
            f"These must be checked for compatibility with the target "
            f"version during impact analysis.\n\n"
        )

    report.append(
        "**Phase A (Data Collection) is now complete.**\n\n"
        "**Next step:** call `get_repo_mapping` to map CS packages "
        "to their source repositories, then `clone_package_repos`."
    )

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

    return "\n".join(report)
