"""
HubSpot snapshot helpers (desktop-first, deterministic, read-only).
"""

from __future__ import annotations

import csv
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from g_gremlin import __version__
from g_gremlin.hubspot import HubSpotClient
from g_gremlin.hubspot_lists import export_list_spec, get_list, list_lists, list_members
from g_gremlin.hubspot_pipelines import export_pipeline_spec, get_pipeline, list_pipelines
from g_gremlin.hubspot_schema import HubSpotSchemaCache
from g_gremlin.hubspot_workflows import (
    AutomationBetaDisabledError,
    get_flow,
    list_flows,
)


@dataclass
class SnapshotResult:
    out_dir: Path
    meta_path: Path
    properties_paths: List[Path]
    pipeline_paths: List[Path]
    list_paths: List[Path]
    workflow_paths: List[Path]
    list_member_exports: List[Path]
    counts: Dict[str, int]

    def to_dict(self) -> Dict[str, Any]:
        return {
            "out_dir": str(self.out_dir),
            "meta_path": str(self.meta_path),
            "properties_paths": [str(p) for p in self.properties_paths],
            "pipeline_paths": [str(p) for p in self.pipeline_paths],
            "list_paths": [str(p) for p in self.list_paths],
            "workflow_paths": [str(p) for p in self.workflow_paths],
            "list_member_exports": [str(p) for p in self.list_member_exports],
            "counts": self.counts,
        }


class HubSpotSnapshotter:
    def __init__(self, client: HubSpotClient) -> None:
        self.client = client

    def snapshot(
        self,
        out_dir: Path,
        *,
        include_workflows: bool,
        list_ids: Optional[List[str]] = None,
        pipeline_object: str = "deals",
        pipeline_ids: Optional[List[str]] = None,
        workflow_ids: Optional[List[str]] = None,
        include_custom_objects: bool = True,
        include_list_members: bool = False,
    ) -> SnapshotResult:
        out_dir = out_dir.resolve()
        out_dir.mkdir(parents=True, exist_ok=True)

        identity = self.client.whoami()
        portal_id = identity.get("portal_id") or ""
        hub_domain = identity.get("hub_domain")
        time_zone = identity.get("time_zone")

        notes: List[str] = []
        properties_paths: List[Path] = []
        pipeline_paths: List[Path] = []
        list_paths: List[Path] = []
        workflow_paths: List[Path] = []
        member_exports: List[Path] = []

        # Properties (spec-shaped)
        properties_dir = out_dir / "properties"
        properties_dir.mkdir(parents=True, exist_ok=True)
        properties_count = 0

        object_types = ["contacts", "companies", "deals"]
        custom_objects: List[str] = []
        if include_custom_objects:
            try:
                cache = HubSpotSchemaCache(portal_id)
                if cache.is_stale():
                    cache.sync(self.client)
                custom_objects = [o.name for o in cache.get_objects() if o.is_custom and o.name]
            except Exception:
                notes.append("custom_objects_unavailable")

        for obj_type in object_types:
            props = self.client.get_properties(obj_type)
            properties_count += _write_properties_spec(properties_dir / f"{obj_type}.json", obj_type, props)
            properties_paths.append(properties_dir / f"{obj_type}.json")

        if custom_objects:
            spec_path = properties_dir / "custom_objects.json"
            payload = {"version": 1, "objects": {}}
            for obj_type in sorted(custom_objects):
                props = self.client.get_properties(obj_type)
                payload["objects"][obj_type] = {
                    "groups": [],
                    "properties": _normalize_properties(props),
                }
            self.write_json(spec_path, payload)
            properties_paths.append(spec_path)
            properties_count += sum(
                len(payload["objects"][obj]["properties"]) for obj in payload["objects"]
            )

        # Pipelines
        pipelines_dir = out_dir / "pipelines"
        pipelines_dir.mkdir(parents=True, exist_ok=True)
        pipelines = list_pipelines(self.client, pipeline_object)
        if pipeline_ids:
            pipelines = [p for p in pipelines if str(p.get("id")) in set(pipeline_ids)]
        pipelines_count = 0
        for pipeline in sorted(pipelines, key=lambda p: str(p.get("id") or "")):
            pipeline_id = str(pipeline.get("id") or "")
            full = get_pipeline(self.client, pipeline_object, pipeline_id)
            spec = export_pipeline_spec(pipeline_object, full)
            path = pipelines_dir / f"{pipeline_object}_{pipeline_id}.json"
            self.write_json(path, spec)
            pipeline_paths.append(path)
            pipelines_count += 1

        # Lists
        lists_dir = out_dir / "lists"
        lists_dir.mkdir(parents=True, exist_ok=True)
        lists = list_lists(self.client, limit=None)
        if list_ids:
            lists = [l for l in lists if str(l.get("listId") or l.get("id")) in set(list_ids)]
        lists_count = 0
        for item in sorted(lists, key=lambda l: str(l.get("listId") or l.get("id") or "")):
            list_id = str(item.get("listId") or item.get("id") or "")
            full = get_list(self.client, list_id)
            spec = export_list_spec(full)
            path = lists_dir / f"{list_id}.json"
            self.write_json(path, spec)
            list_paths.append(path)
            lists_count += 1

            if include_list_members:
                members_dir = lists_dir / "members"
                members_dir.mkdir(parents=True, exist_ok=True)
                members = list_members(self.client, list_id)
                out_path = members_dir / f"{list_id}_members.csv"
                _write_list_members_csv(list_id, members, out_path)
                member_exports.append(out_path)

        # Workflows
        workflows_count = 0
        workflows_dir = out_dir / "workflows"
        workflows_included = include_workflows
        if include_workflows:
            try:
                workflows_dir.mkdir(parents=True, exist_ok=True)
                flows = list_flows(self.client, limit=None)
                if workflow_ids:
                    flows = [f for f in flows if str(f.get("id")) in set(workflow_ids)]
                for flow in sorted(flows, key=lambda f: str(f.get("id") or "")):
                    flow_id = str(flow.get("id") or "")
                    payload = get_flow(self.client, flow_id)
                    path = workflows_dir / f"{flow_id}.json"
                    self.write_json(path, payload)
                    workflow_paths.append(path)
                    workflows_count += 1
            except AutomationBetaDisabledError:
                workflows_included = False
                notes.append("workflows_skipped_beta_flag")
        else:
            workflows_included = False

        meta = {
            "created_at": datetime.now(timezone.utc).isoformat(),
            "portal_id": portal_id,
            "hub_domain": hub_domain,
            "time_zone": time_zone,
            "g_gremlin_version": __version__,
            "included": {
                "properties": True,
                "pipelines": True,
                "lists": True,
                "workflows": workflows_included,
            },
            "filters": {
                "pipeline_object": pipeline_object,
                "pipeline_ids": pipeline_ids or [],
                "list_ids": list_ids or [],
                "workflow_ids": workflow_ids or [],
                "include_custom_objects": include_custom_objects,
                "include_list_members": include_list_members,
            },
            "counts": {
                "properties_objects": properties_count,
                "pipelines": pipelines_count,
                "lists": lists_count,
                "workflows": workflows_count,
            },
            "warnings": notes,
        }
        meta_path = out_dir / "meta.json"
        self.write_json(meta_path, meta)

        return SnapshotResult(
            out_dir=out_dir,
            meta_path=meta_path,
            properties_paths=properties_paths,
            pipeline_paths=pipeline_paths,
            list_paths=list_paths,
            workflow_paths=workflow_paths,
            list_member_exports=member_exports,
            counts=meta["counts"],
        )

    @staticmethod
    def write_json(path: Path, obj: Dict[str, Any]) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(obj, indent=2, sort_keys=True), encoding="utf-8")


def _normalize_properties(properties: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    specs = []
    for prop in sorted(properties, key=lambda p: str(p.get("name") or "")):
        specs.append(
            {
                "name": prop.get("name"),
                "label": prop.get("label"),
                "type": prop.get("type"),
                "field_type": prop.get("fieldType"),
                "group_name": prop.get("groupName"),
                "description": prop.get("description"),
                "options": prop.get("options") or [],
                "has_unique_value": prop.get("hasUniqueValue"),
                "archived": bool(prop.get("archived")),
            }
        )
    return specs


def _write_properties_spec(path: Path, object_type: str, properties: List[Dict[str, Any]]) -> int:
    payload = {
        "version": 1,
        "objects": {
            object_type: {
                "groups": [],
                "properties": _normalize_properties(properties),
            }
        },
    }
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
    return len(payload["objects"][object_type]["properties"])


def _write_list_members_csv(list_id: str, members, out_path: Path) -> None:
    out_path.parent.mkdir(parents=True, exist_ok=True)
    rows = []
    for member in members:
        rows.append(
            {
                "list_id": str(list_id),
                "record_id": member.get("recordId") or member.get("record_id"),
                "membership_timestamp": member.get("membershipTimestamp")
                or member.get("membership_timestamp"),
            }
        )
    with out_path.open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(
            f,
            fieldnames=["list_id", "record_id", "membership_timestamp"],
        )
        writer.writeheader()
        writer.writerows(rows)
