"""
Format transformers for webhook payloads.

Cosmetic transformations only - no auto-imposed headers.
Headers are user-supplied via profile.
"""

from __future__ import annotations

from typing import Any, Callable, Dict, List, Tuple

from g_gremlin.webhook_out.envelope import WebhookEnvelope


# Type alias: Transformer returns (body_dict, headers_dict)
Transformer = Callable[[WebhookEnvelope], Tuple[Dict[str, Any], Dict[str, str]]]


def _flatten_dict(
    d: Dict[str, Any],
    parent_key: str = "",
    sep: str = "_",
    max_depth: int = 3,
) -> Dict[str, Any]:
    """
    Flatten nested dict for platforms that prefer flat structures.

    Limits depth to prevent explosion on deeply nested payloads.
    Arrays with <=5 simple elements are preserved; larger arrays show count.
    """
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k

        if isinstance(v, dict) and max_depth > 0:
            items.extend(_flatten_dict(v, new_key, sep, max_depth - 1).items())
        elif isinstance(v, list):
            # Keep small arrays of primitives; stringify large ones
            if len(v) <= 5 and all(isinstance(x, (str, int, float, bool, type(None))) for x in v):
                items.append((new_key, v))
            else:
                items.append((f"{new_key}_count", len(v)))
                # Include first item as sample if available
                if v and isinstance(v[0], (str, int, float, bool)):
                    items.append((f"{new_key}_first", v[0]))
        else:
            items.append((new_key, v))

    return dict(items)


def transform_flat(envelope: WebhookEnvelope) -> Tuple[Dict[str, Any], Dict[str, str]]:
    """
    Flat format transformer.

    Flattens nested JSON and lifts top-level fields for easier
    consumption by platforms like Zapier that prefer flat structures.
    """
    full_dict = envelope.to_dict()
    payload = full_dict.get("payload", {})

    # Flatten the payload portion
    flat_payload = _flatten_dict(payload)

    # Build result with envelope metadata at top level
    result = {
        "event_type": full_dict["event_type"],
        "correlation_id": full_dict["correlation_id"],
        "delivery_id": full_dict["delivery_id"],
        "source": full_dict["source"],
        "timestamp": full_dict["timestamp"],
        # Flattened payload fields
        **flat_payload,
    }

    headers = {"Content-Type": "application/json"}
    return result, headers


def transform_wrapped(envelope: WebhookEnvelope) -> Tuple[Dict[str, Any], Dict[str, str]]:
    """
    Wrapped format transformer.

    Wraps payload in a `data:[]` array for platforms like Make
    that handle arrays better than nested objects.
    """
    full_dict = envelope.to_dict()

    result = {
        "data": [full_dict],
        "meta": {
            "event_type": full_dict["event_type"],
            "correlation_id": full_dict["correlation_id"],
            "delivery_id": full_dict["delivery_id"],
            "source": full_dict["source"],
            "timestamp": full_dict["timestamp"],
        },
    }

    headers = {"Content-Type": "application/json"}
    return result, headers


def transform_generic(envelope: WebhookEnvelope) -> Tuple[Dict[str, Any], Dict[str, str]]:
    """
    Generic/default transformer.

    Passes through the full WebhookEnvelope as-is.
    Works well with n8n, Pipedream, and custom webhooks.
    """
    headers = {"Content-Type": "application/json"}
    return envelope.to_dict(), headers


def _format_category(label: str) -> str:
    text = label.replace("_", " ").strip()
    return text.title() if text else "Other"


def _artifact_link(path: str, base_url: str | None, output_dir: str | None) -> str:
    if not path:
        return ""
    if base_url and output_dir:
        try:
            from pathlib import Path
            from urllib.parse import quote

            output_path = Path(output_dir).resolve()
            artifact_path = Path(path).resolve()
            if artifact_path.is_relative_to(output_path):
                rel = artifact_path.relative_to(output_path)
                rel_text = rel.as_posix().lstrip("/")
                base = base_url.rstrip("/")
                rel_encoded = quote(rel_text, safe="/")
                return f"{base}/{quote(output_path.name, safe='')}/{rel_encoded}"
        except Exception:
            pass
    return path


def _build_scout_digest(envelope: WebhookEnvelope) -> Dict[str, Any]:
    payload = envelope.payload
    result = payload.get("result", {}) if isinstance(payload, dict) else {}
    meta = result.get("meta", {}) if isinstance(result, dict) else {}
    artifacts = result.get("artifacts", {}) if isinstance(result, dict) else {}
    only_new = bool(meta.get("only_new"))
    crm = str(meta.get("crm") or "").upper() or "CRM"
    org_alias = meta.get("org_alias") or "default"
    top_n = meta.get("top_n")
    duration_ms = result.get("total_duration_ms") or result.get("duration_ms")
    base_url = meta.get("artifact_base_url")
    output_dir = meta.get("output_dir")

    schema = result.get("$schema") or payload.get("$schema")
    findings: List[Dict[str, Any]] = []
    if schema == "ScoutScanResult/v1":
        findings = result.get("findings", []) or []
    elif schema == "ScoutFinding/v1":
        findings = [result]

    total_findings = sum(int(f.get("findings_count", 0) or 0) for f in findings)

    header = f"🧹 Scout Digest — {crm}"
    if org_alias:
        header = f"{header} — {org_alias}"
    run_id = result.get("run_id")
    started_at = result.get("started_at")
    if run_id or started_at:
        suffix = []
        if started_at:
            suffix.append(str(started_at).replace("T", " ").split(".")[0])
        if run_id:
            suffix.append(f"run_id={run_id}")
        header = f"{header} — {' • '.join(suffix)}"

    blocks: List[Dict[str, Any]] = [
        {"type": "header", "text": {"type": "plain_text", "text": header}},
    ]

    summary_lines: List[str] = []
    category_totals: Dict[str, int] = {}
    for finding in findings:
        category = str(finding.get("category") or "other")
        category_totals[category] = category_totals.get(category, 0) + int(
            finding.get("findings_count", 0) or 0
        )

    for category, count in sorted(category_totals.items()):
        label = _format_category(category)
        if only_new:
            summary_lines.append(f"*{label}:* {count} (+{count})")
        else:
            summary_lines.append(f"*{label}:* {count}")

    if not summary_lines:
        summary_lines = ["*No findings.*"]

    blocks.append(
        {
            "type": "section",
            "text": {"type": "mrkdwn", "text": "*Summary*\n" + "\n".join(summary_lines)},
        }
    )

    highlight_lines: List[str] = []
    total_highlights = 0
    max_per_category = MAX_HIGHLIGHTS_PER_CATEGORY
    max_total = MAX_HIGHLIGHTS_TOTAL
    for finding in findings:
        evidence = finding.get("evidence", []) or []
        if not evidence:
            continue
        scout_name = finding.get("display_name") or finding.get("scout_name") or "Scout"
        per_category = 0
        for row in evidence:
            if per_category >= max_per_category or total_highlights >= max_total:
                break
            reason_code = row.get("reason_code") or ""
            record_id = row.get("record_id") or ""
            bits = [str(scout_name)]
            if reason_code:
                bits.append(str(reason_code))
            if record_id:
                bits.append(f"`{record_id}`")
            highlight_lines.append(" — ".join(bits))
            total_highlights += 1
            per_category += 1
        if total_highlights >= max_total:
            break

    if highlight_lines:
        for finding in findings:
            evidence = finding.get("evidence", []) or []
            if not evidence:
                continue
            scout_name = finding.get("display_name") or finding.get("scout_name") or "Scout"
            if len(evidence) > max_per_category:
                remaining = len(evidence) - max_per_category
                highlight_lines.append(f"{scout_name}: +{remaining} more (see CSV)")

    if highlight_lines:
        blocks.append(
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": "*Highlights (new only)*\n" + "\n".join(highlight_lines)},
            }
        )

    artifact_lines: List[str] = []
    output_path = artifacts.get("output_path")
    prov_path = artifacts.get("provenance_path")
    csv_path = artifacts.get("csv_path")
    if csv_path:
        artifact_lines.append(f"*CSV:* {_artifact_link(str(csv_path), base_url, output_dir)}")
    if output_path:
        artifact_lines.append(f"*JSON:* {_artifact_link(str(output_path), base_url, output_dir)}")
    if prov_path:
        artifact_lines.append(f"*Provenance:* {_artifact_link(str(prov_path), base_url, output_dir)}")

    if artifact_lines:
        blocks.append(
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": "*Artifacts*\n" + "\n".join(artifact_lines)},
            }
        )

    footer_bits: List[str] = []
    if duration_ms:
        footer_bits.append(f"{int(duration_ms)} ms")
    if only_new:
        footer_bits.append("only_new=true")
    if top_n is not None:
        footer_bits.append(f"top_n={top_n}")
    if total_findings == 0:
        footer_bits.append("✅ 0 new issues")
    if footer_bits:
        blocks.append(
            {
                "type": "context",
                "elements": [{"type": "mrkdwn", "text": " • ".join(footer_bits)}],
            }
        )

    text = f"Scout digest: {total_findings} findings"
    return {"text": text, "blocks": blocks}


def transform_slack_blockkit(envelope: WebhookEnvelope) -> Tuple[Dict[str, Any], Dict[str, str]]:
    """
    Slack Block Kit transformer for Scout/AgenticResult payloads.

    Returns a Slack-compatible payload with `text` and `blocks`.
    """
    payload = envelope.payload
    if isinstance(payload, dict):
        result = payload.get("result", {})
        schema = result.get("$schema") if isinstance(result, dict) else None
        if schema in ("ScoutScanResult/v1", "ScoutFinding/v1"):
            body = _build_scout_digest(envelope)
            return body, {"Content-Type": "application/json"}

    text = f"{envelope.event_type} ({payload.get('status', 'unknown')})"
    blocks = [
        {"type": "section", "text": {"type": "mrkdwn", "text": f"*{envelope.event_type}*"}},
        {"type": "context", "elements": [{"type": "mrkdwn", "text": text}]},
    ]
    return {"text": text, "blocks": blocks}, {"Content-Type": "application/json"}


MAX_HIGHLIGHTS_PER_CATEGORY = 3
MAX_HIGHLIGHTS_TOTAL = 12


# Registry mapping format names to transformers
TRANSFORMERS: Dict[str, Transformer] = {
    "flat": transform_flat,
    "wrapped": transform_wrapped,
    "generic": transform_generic,
    "slack": transform_slack_blockkit,
    "slack_blockkit": transform_slack_blockkit,
}


def get_transformer(format_name: str | None) -> Transformer:
    """
    Get transformer by format name.

    Falls back to generic if format is None or unknown.
    """
    if format_name is None:
        return transform_generic
    return TRANSFORMERS.get(format_name.lower(), transform_generic)
