"""Deactivate suffix-only polluted collection taxonomy nodes and purge remote catalog rows."""

from __future__ import annotations

import argparse
import json
import logging
from typing import Any, Dict, Iterable, Optional, Set

from db.collection_path_guard import (
    ORPHAN_DEFAULT_PURGE_TIERS,
    list_orphan_remote_collections,
    list_polluted_collection_paths,
    list_polluted_remote_catalog_targets,
    list_polluted_remote_targets_from_taxonomy,
)
from db.master_taxonomy_delete import deactivate_taxonomy_node
from db.master_taxonomy_remote_purge import purge_remote_catalog_targets, resolve_channel_clients_for_purge
from db.session import get_session

logger = logging.getLogger(__name__)


def prune_polluted_collections(
    session,
    *,
    hub_path_slug: str = "kitchen-cabinets",
    purge_remote: bool = False,
    dry_run: bool = True,
    magento_connection_id: Optional[int] = None,
    shopify_connection_id: Optional[int] = None,
    include_inactive_nodes: bool = True,
    remote_only: bool = False,
    include_orphans: bool = False,
    orphan_tiers: Optional[Iterable[str]] = None,
    taxonomy_links_only: bool = False,
    channels: Optional[Iterable[str]] = None,
    exclude_remote_ids: Optional[Iterable[str]] = None,
    forget_stale_taxonomy_links: bool = False,
) -> Dict[str, Any]:
    channel_filter = {
        str(channel or "").strip().lower()
        for channel in (channels or [])
        if str(channel or "").strip()
    }
    excluded = _parse_remote_id_excludes(exclude_remote_ids or [])
    polluted_local = [] if remote_only else list_polluted_collection_paths(
        session,
        hub_path_slug=hub_path_slug,
        include_inactive=include_inactive_nodes,
    )
    if taxonomy_links_only:
        remote_targets = list_polluted_remote_targets_from_taxonomy(
            session,
            hub_path_slug=hub_path_slug,
            magento_connection_id=magento_connection_id,
            shopify_connection_id=shopify_connection_id,
            include_inactive_nodes=include_inactive_nodes,
        )
    else:
        remote_targets = list_polluted_remote_catalog_targets(
            session,
            hub_path_slug=hub_path_slug,
            magento_connection_id=magento_connection_id,
            shopify_connection_id=shopify_connection_id,
            include_inactive_nodes=include_inactive_nodes,
        )

    if channel_filter:
        remote_targets = [
            target
            for target in remote_targets
            if str(target.get("channel_code") or "").strip().lower() in channel_filter
        ]
    if excluded:
        remote_targets = [
            target
            for target in remote_targets
            if not _is_excluded_remote_target(target, excluded)
        ]

    orphans: list = []
    orphan_purge_tiers: Set[str] = set(orphan_tiers) if orphan_tiers is not None else set(ORPHAN_DEFAULT_PURGE_TIERS)
    orphan_purge_targets: list = []
    if include_orphans and not taxonomy_links_only:
        orphans = list_orphan_remote_collections(
            session,
            hub_path_slug=hub_path_slug,
            magento_connection_id=magento_connection_id,
            shopify_connection_id=shopify_connection_id,
        )
        orphan_purge_targets = [row for row in orphans if row.get("orphan_tier") in orphan_purge_tiers]
        if channel_filter:
            orphans = [
                row
                for row in orphans
                if str(row.get("channel_code") or "").strip().lower() in channel_filter
            ]
            orphan_purge_targets = [
                row
                for row in orphan_purge_targets
                if str(row.get("channel_code") or "").strip().lower() in channel_filter
            ]
        if excluded:
            orphans = [row for row in orphans if not _is_excluded_remote_target(row, excluded)]
            orphan_purge_targets = [
                row for row in orphan_purge_targets if not _is_excluded_remote_target(row, excluded)
            ]

    combined_purge_targets = list(remote_targets) + orphan_purge_targets

    result: Dict[str, Any] = {
        "dry_run": dry_run,
        "hub_path_slug": hub_path_slug,
        "remote_only": remote_only,
        "taxonomy_links_only": taxonomy_links_only,
        "forget_stale_taxonomy_links": forget_stale_taxonomy_links,
        "channels": sorted(channel_filter) if channel_filter else [],
        "excluded_remote_ids": sorted(
            f"{channel}:{remote_id}" for channel, remote_id in excluded
        ),
        "polluted_count": len(polluted_local),
        "polluted": polluted_local,
        "remote_target_count": len(remote_targets),
        "remote_targets": remote_targets,
        "include_orphans": include_orphans,
        "orphans_skipped_reason": "taxonomy_links_only" if include_orphans and taxonomy_links_only else None,
        "orphan_purge_tiers": sorted(orphan_purge_tiers),
        "orphan_count": len(orphans),
        "orphans": orphans,
        "orphan_purge_target_count": len(orphan_purge_targets),
        "deactivated": [],
        "remote_purge": None,
        "errors": [],
    }

    if not remote_only:
        for row in polluted_local:
            if row.get("is_active") is False:
                continue
            node_id = int(row["node_id"])
            if dry_run:
                result["deactivated"].append(
                    {"node_id": node_id, "path_slug": row["path_slug"], "status": "would_deactivate"}
                )
                continue
            try:
                stats = deactivate_taxonomy_node(
                    session,
                    node_id,
                    note="deactivated by prune_polluted_collections",
                    purge_remote=purge_remote,
                    magento_api=None,
                    shopify_client=None,
                    dry_run=False,
                )
                result["deactivated"].append({"node_id": node_id, "path_slug": row["path_slug"], **stats})
            except Exception as exc:
                result["errors"].append({"node_id": node_id, "path_slug": row["path_slug"], "error": str(exc)})

    if forget_stale_taxonomy_links and combined_purge_targets:
        result["remote_purge"] = _forget_stale_taxonomy_links(
            session,
            combined_purge_targets,
            dry_run=dry_run,
        )
    elif purge_remote and combined_purge_targets:
        magento_api, shopify_client = resolve_channel_clients_for_purge(
            session,
            magento_connection_id=magento_connection_id,
            shopify_connection_id=shopify_connection_id,
        )
        result["remote_purge"] = purge_remote_catalog_targets(
            targets=combined_purge_targets,
            magento_api=magento_api,
            shopify_client=shopify_client,
            dry_run=dry_run,
            session=session,
        )
    elif purge_remote and not combined_purge_targets:
        result["remote_purge"] = {
            "dry_run": dry_run,
            "magento_deleted": 0,
            "shopify_deleted": 0,
            "already_deleted": 0,
            "links_deactivated": 0,
            "skipped": 0,
            "errors": [],
            "actions": [],
            "note": "No remote targets found in inactive taxonomy links, channel registries, or orphan scan.",
        }

    return result


def _forget_stale_taxonomy_links(session, targets: Iterable[Dict[str, Any]], *, dry_run: bool) -> Dict[str, Any]:
    """Remove inactive taxonomy channel links for remotes confirmed gone outside the API.

    This is intentionally narrower than a normal purge: it only touches target rows
    that came from deactivated taxonomy links and whose link row is already inactive.
    """
    from sqlalchemy import select

    from db.models import MasterTaxonomyChannelLink

    link_ids = sorted(
        {
            int(target["link_id"])
            for target in targets
            if target.get("source") == "taxonomy_link"
            and str(target.get("link_id") or "").isdigit()
            and target.get("link_is_active") is False
        }
    )
    result: Dict[str, Any] = {
        "dry_run": dry_run,
        "mode": "forget_stale_taxonomy_links",
        "candidate_count": len(link_ids),
        "found_count": 0,
        "would_forget": 0,
        "forgotten": 0,
        "skipped_active_links": 0,
        "missing_ids": [],
        "actions": [],
        "errors": [],
    }
    if not link_ids:
        return result
    rows = list(
        session.scalars(
            select(MasterTaxonomyChannelLink).where(MasterTaxonomyChannelLink.id.in_(link_ids))
        ).all()
    )
    found_ids = {int(row.id) for row in rows}
    result["found_count"] = len(rows)
    result["missing_ids"] = [link_id for link_id in link_ids if link_id not in found_ids]
    for row in rows:
        action = {
            "channel": row.channel_code,
            "connection_id": row.connection_id,
            "remote_id": row.remote_id,
            "link_id": row.id,
            "taxonomy_node_id": row.taxonomy_node_id,
        }
        if row.is_active:
            result["skipped_active_links"] += 1
            action["status"] = "skipped_active_link"
            result["actions"].append(action)
            continue
        if dry_run:
            result["would_forget"] += 1
            action["status"] = "would_forget"
            result["actions"].append(action)
            continue
        session.delete(row)
        result["forgotten"] += 1
        action["status"] = "forgotten"
        result["actions"].append(action)
    if not dry_run:
        session.flush()
    return result


def _parse_remote_id_excludes(values: Iterable[str]) -> Set[tuple[str, str]]:
    excluded: Set[tuple[str, str]] = set()
    for value in values:
        text = str(value or "").strip()
        if not text:
            continue
        if ":" in text:
            channel, remote_id = text.split(":", 1)
            channel = channel.strip().lower()
            remote_id = remote_id.strip()
            if channel and remote_id:
                excluded.add((channel, remote_id))
            continue
        excluded.add(("*", text))
    return excluded


def _is_excluded_remote_target(target: Dict[str, Any], excluded: Set[tuple[str, str]]) -> bool:
    channel = str(target.get("channel_code") or "").strip().lower()
    remote_id = str(target.get("remote_id") or "").strip()
    return ("*", remote_id) in excluded or (channel, remote_id) in excluded


def main() -> None:
    parser = argparse.ArgumentParser(description="Deactivate suffix-only polluted kitchen collection paths")
    parser.add_argument("--hub-path-slug", default="kitchen-cabinets")
    parser.add_argument("--apply", action="store_true", help="Apply deactivations (default is dry-run)")
    parser.add_argument(
        "--purge-remote",
        action="store_true",
        help="Delete Magento categories / Shopify collections (scans inactive nodes + registries)",
    )
    parser.add_argument(
        "--remote-only",
        action="store_true",
        help="Skip local taxonomy deactivation; only scan and purge remote catalog rows",
    )
    parser.add_argument(
        "--include-orphans",
        action="store_true",
        help="Also scan remote-only collections/categories not managed by our taxonomy",
    )
    parser.add_argument(
        "--taxonomy-links-only",
        action="store_true",
        help=(
            "Only purge remotes referenced by deactivated polluted taxonomy links; "
            "skip registry-only not_in_taxonomy and orphan candidates."
        ),
    )
    parser.add_argument(
        "--channels",
        nargs="+",
        choices=["magento", "shopify"],
        default=None,
        help="Limit remote targets to selected channel(s). Default includes both.",
    )
    parser.add_argument(
        "--exclude-remote-id",
        action="append",
        default=None,
        help=(
            "Skip a remote id during purge. Use either '161' for any channel or "
            "'magento:161' / 'shopify:gid://...' for a channel-specific skip. Repeatable."
        ),
    )
    parser.add_argument(
        "--forget-stale-taxonomy-links",
        action="store_true",
        help=(
            "Do not call Magento/Shopify. Locally remove inactive taxonomy channel links "
            "for the selected stale targets after confirming the remotes are already gone."
        ),
    )
    parser.add_argument(
        "--orphan-tier",
        action="append",
        choices=["test", "empty_unlinked", "review", "keep"],
        default=None,
        help="Orphan tiers eligible for purge (repeatable). Default: test only.",
    )
    parser.add_argument("--magento-connection-id", type=int, default=None)
    parser.add_argument("--shopify-connection-id", type=int, default=None)
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO)
    with get_session() as session:
        result = prune_polluted_collections(
            session,
            hub_path_slug=args.hub_path_slug,
            purge_remote=args.purge_remote,
            dry_run=not args.apply,
            magento_connection_id=args.magento_connection_id,
            shopify_connection_id=args.shopify_connection_id,
            include_inactive_nodes=True,
            remote_only=args.remote_only,
            include_orphans=args.include_orphans,
            orphan_tiers=args.orphan_tier,
            taxonomy_links_only=args.taxonomy_links_only,
            channels=args.channels,
            exclude_remote_ids=args.exclude_remote_id,
            forget_stale_taxonomy_links=args.forget_stale_taxonomy_links,
        )
        if args.apply:
            session.commit()
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
