"""
HubSpot Automation (v4) workflow helpers with beta guardrails.
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple

from g_gremlin.base import AuthenticationError, IntegrationError, RateLimitError, TransientError
from g_gremlin.hubspot import HubSpotClient
from g_gremlin.hubspot_infra import HubSpotInfraError, _request_with_scope_handling

AUTOMATION_BETA_ENV = "G_GREMLIN_HUBSPOT_AUTOMATION_BETA"
AUTOMATION_BASE_URL = "https://api.hubapi.com/automation/v4/flows"


class AutomationBetaDisabledError(HubSpotInfraError):
    """Raised when automation beta flag is not enabled."""


class WorkflowApplyError(HubSpotInfraError):
    """Raised when workflow apply fails."""


@dataclass
class WorkflowDiffResult:
    flow_id: str
    diff: str

    def to_dict(self) -> Dict[str, Any]:
        return {"flow_id": self.flow_id, "diff": self.diff}


def require_automation_beta() -> None:
    if os.getenv(AUTOMATION_BETA_ENV) != "1":
        raise AutomationBetaDisabledError(
            f"Automation v4 is beta. Set {AUTOMATION_BETA_ENV}=1 to enable."
        )


def list_flows(client: HubSpotClient, limit: Optional[int] = None) -> List[Dict[str, Any]]:
    require_automation_beta()
    params = {"limit": min(limit or 100, 100)}
    flows: List[Dict[str, Any]] = []
    for item in _paginate(client, AUTOMATION_BASE_URL, params):
        flows.append(item)
        if limit and len(flows) >= limit:
            break
    return flows


def get_flow(client: HubSpotClient, flow_id: str) -> Dict[str, Any]:
    require_automation_beta()
    url = f"{AUTOMATION_BASE_URL}/{flow_id}"
    try:
        resp = _request_with_scope_handling(client, "GET", url)
        return resp.json()
    except (AuthenticationError, IntegrationError, RateLimitError, TransientError) as exc:
        raise HubSpotInfraError(str(exc)) from exc


def create_flow(client: HubSpotClient, payload: Dict[str, Any]) -> Dict[str, Any]:
    require_automation_beta()
    try:
        resp = _request_with_scope_handling(client, "POST", AUTOMATION_BASE_URL, json_body=payload)
        return resp.json()
    except (AuthenticationError, IntegrationError, RateLimitError, TransientError) as exc:
        raise HubSpotInfraError(str(exc)) from exc


def update_flow(client: HubSpotClient, flow_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    require_automation_beta()
    url = f"{AUTOMATION_BASE_URL}/{flow_id}"
    try:
        resp = _request_with_scope_handling(client, "PUT", url, json_body=payload)
        return resp.json()
    except (AuthenticationError, IntegrationError, RateLimitError, TransientError) as exc:
        raise HubSpotInfraError(str(exc)) from exc


def delete_flow(client: HubSpotClient, flow_id: str) -> None:
    require_automation_beta()
    url = f"{AUTOMATION_BASE_URL}/{flow_id}"
    try:
        _request_with_scope_handling(client, "DELETE", url)
    except (AuthenticationError, IntegrationError, RateLimitError, TransientError) as exc:
        raise HubSpotInfraError(str(exc)) from exc


def diff_flow(local_payload: Dict[str, Any], remote_payload: Dict[str, Any]) -> str:
    local_dump = _canonical_json(local_payload)
    remote_dump = _canonical_json(remote_payload)
    if local_dump == remote_dump:
        return ""
    import difflib

    diff = difflib.unified_diff(
        remote_dump.splitlines(),
        local_dump.splitlines(),
        fromfile="remote",
        tofile="local",
        lineterm="",
    )
    return "\n".join(diff)


def load_flow_file(path: Path) -> Dict[str, Any]:
    if not path.exists():
        raise HubSpotInfraError(f"Workflow file not found: {path}")
    if path.suffix.lower() not in {".json"}:
        raise HubSpotInfraError("Workflow files must be JSON.")
    return json.loads(path.read_text(encoding="utf-8"))


def resolve_flow_id(payload: Dict[str, Any]) -> Optional[str]:
    return payload.get("id") or payload.get("flowId")


def ensure_revision_or_raise(local_revision: Optional[str], latest_revision: Optional[str], force: bool) -> str:
    if not latest_revision:
        raise WorkflowApplyError("Latest revisionId missing from remote flow.")
    if not local_revision and not force:
        raise WorkflowApplyError("Local revisionId missing. Re-pull the flow or use --force.")
    if local_revision and local_revision != latest_revision and not force:
        raise WorkflowApplyError(
            f"Revision mismatch. Local={local_revision} Remote={latest_revision}. Use --force to override."
        )
    return latest_revision


def validate_flow_payload(payload: Dict[str, Any], *, mode: str) -> None:
    required: List[str]
    if mode == "create":
        required = ["type", "flowType", "actions"]
    elif mode == "update":
        required = ["type", "revisionId"]
    else:
        raise WorkflowApplyError(f"Unknown validation mode: {mode}")

    missing = [key for key in required if payload.get(key) in (None, "")]
    if missing:
        missing_str = ", ".join(missing)
        raise WorkflowApplyError(
            f"Workflow payload missing required fields: {missing_str}. "
            "Export a baseline with `g-gremlin hubspot workflows pull` first."
        )


def backup_flow(flow_id: str, payload: Dict[str, Any], backup_dir: Path) -> Path:
    backup_dir.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    path = backup_dir / f"{flow_id}-{timestamp}.json"
    path.write_text(_canonical_json(payload), encoding="utf-8")
    return path


def _paginate(client: HubSpotClient, url: str, params: Dict[str, Any]) -> Iterable[Dict[str, Any]]:
    cursor: Optional[str] = None
    while True:
        page_params = dict(params)
        if cursor:
            page_params["after"] = cursor
        resp = _request_with_scope_handling(client, "GET", url, params=page_params)
        payload = resp.json()
        results = payload.get("results", []) or []
        for item in results:
            yield item
        paging = payload.get("paging", {}) or {}
        next_link = paging.get("next", {}) or {}
        cursor = next_link.get("after")
        if not cursor:
            break


def _canonical_json(payload: Dict[str, Any]) -> str:
    return json.dumps(payload, indent=2, sort_keys=True)
