"""Dynamics 365 destructive manifest handling.

Maps to actual pac safety switches:
- pac solution unpack --allowDelete Yes  -> gated by destructive.yaml + allow_unpack_delete
- pac solution import --force-overwrite  -> gated by destructive.yaml + allow_force_overwrite

Important: In unmanaged solutions, removing a component from the solution XML
does NOT delete it from the environment. It's only "detached." True deletion
requires Dataverse API calls. Managed solution upgrade CAN remove components.
MVP focuses on what's enforceable via pac flags.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional
import json

from g_gremlin.errors import missing_pyyaml_error


@dataclass
class DynamicsDestructiveManifest:
    # pac solution unpack --allowDelete gating
    allow_unpack_delete_files: List[str] = field(default_factory=list)
    # pac solution import --force-overwrite gating
    force_overwrite_components: List[Dict[str, str]] = field(default_factory=list)
    # Informational: components to detach from solution (not deleted from env)
    detach_components: List[Dict[str, str]] = field(default_factory=list)

    def allows_unpack_delete(self) -> bool:
        """Returns True if any files are listed for allowDelete."""
        return len(self.allow_unpack_delete_files) > 0

    def allows_force_overwrite(self) -> bool:
        """Returns True if any components require force-overwrite."""
        return len(self.force_overwrite_components) > 0


def load_destructive_manifest(pack_dir: Path) -> Optional[DynamicsDestructiveManifest]:
    path = pack_dir / "destructive.yaml"
    if not path.exists():
        return None
    raw_text = path.read_text(encoding="utf-8")
    if path.suffix.lower() in {".yaml", ".yml"}:
        try:
            import yaml  # type: ignore
        except Exception as exc:
            raise missing_pyyaml_error() from exc
        payload = yaml.safe_load(raw_text) or {}
    else:
        payload = json.loads(raw_text)

    return DynamicsDestructiveManifest(
        allow_unpack_delete_files=list(payload.get("allow_unpack_delete_files") or []),
        force_overwrite_components=list(payload.get("force_overwrite_components") or []),
        detach_components=list(payload.get("detach_components") or []),
    )


def validate_destructive_coverage(
    manifest: Optional[DynamicsDestructiveManifest],
    pack_options: Dict[str, Any],
    plan: "DynamicsPackPlan",
) -> List[str]:
    """
    Validate dual-key gating:
    1. --allowDelete on unpack requires: manifest entry + options.allow_unpack_delete: true
    2. --force-overwrite on import requires: manifest entry + options.allow_force_overwrite: true
    Returns list of validation errors (empty = OK).
    """
    errors: List[str] = []
    allow_unpack_delete = bool(pack_options.get("allow_unpack_delete"))
    allow_force_overwrite = bool(pack_options.get("allow_force_overwrite"))

    has_manifest = manifest is not None
    has_unpack_listed = manifest.allows_unpack_delete() if manifest else False
    has_force_listed = manifest.allows_force_overwrite() if manifest else False

    if allow_unpack_delete and not has_unpack_listed:
        errors.append("allow_unpack_delete enabled but destructive.yaml has no allow_unpack_delete_files")
    if has_unpack_listed and not allow_unpack_delete:
        errors.append("destructive.yaml allow_unpack_delete_files present but options.allow_unpack_delete is false")

    if allow_force_overwrite and not has_force_listed:
        errors.append("allow_force_overwrite enabled but destructive.yaml has no force_overwrite_components")
    if has_force_listed and not allow_force_overwrite:
        errors.append("destructive.yaml force_overwrite_components present but options.allow_force_overwrite is false")

    _ = plan  # plan reserved for future validations
    return errors
