"""``mgf-cloud-provision`` — plan / apply / destroy a service spec.

    mgf-cloud-provision plan    spec.toml                 # read-only diff
    mgf-cloud-provision apply   spec.toml --yes           # converge (CREATE/REPLACE)
    mgf-cloud-provision destroy spec.toml --yes           # delete the service VM

``--provider fake`` exercises the whole flow against mgf-cloud's in-memory
provider (no cloud, no credentials) — used by the tests + for dry inspection.
The real provider (``--provider infomaniak``) loads credentials via
``mgf.cloud.config.load`` from ``--clouds-yaml`` / the standard search paths.
"""

from __future__ import annotations

import argparse
import sys
from typing import TYPE_CHECKING

from mgf.cloud.providers import get_provider

from mgf.cloud_provision import __version__
from mgf.cloud_provision._provision import apply, destroy, plan
from mgf.cloud_provision._spec import load_spec
from mgf.cloud_provision.exceptions import ProvisionError, SpecError

if TYPE_CHECKING:
    from collections.abc import Sequence

    from mgf.cloud.providers.base import CloudProvider

_EXIT_OK = 0
_EXIT_PROVISION_ERR = 1
_EXIT_USAGE_ERR = 2


def _make_provider(provider: str, clouds_yaml: str | None, cloud: str | None) -> CloudProvider:
    if provider == "fake":
        return get_provider("fake")
    from mgf.cloud import config

    credentials = config.load(clouds_yaml=clouds_yaml, cloud=cloud or "openstack")
    return get_provider(provider, credentials)


def build_parser() -> argparse.ArgumentParser:
    # Shared flags live on a parent parser so they're accepted on either side of
    # the subcommand (`… apply spec --yes` works), mirroring mgf-cloud's CLI.
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("--provider", default="infomaniak", help="provider name (or 'fake')")
    common.add_argument("--cloud", default=None, help="clouds.yaml cloud name")
    common.add_argument("--clouds-yaml", default=None, help="path to an OpenStack clouds.yaml")
    common.add_argument("--yes", action="store_true", help="confirm a mutating apply/destroy")
    common.add_argument(
        "--allow-replace", action="store_true", help="permit a destructive REPLACE on drift"
    )

    parser = argparse.ArgumentParser(
        prog="mgf-cloud-provision",
        description="Declarative provisioning of the federation's self-hosted services.",
    )
    parser.add_argument(
        "--version", action="version", version=f"mgf-cloud-provision {__version__}"
    )
    sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
    for name in ("plan", "apply", "destroy"):
        sp = sub.add_parser(name, parents=[common], help=f"{name} a service spec")
        sp.add_argument("spec", help="path to the service spec .toml")
    return parser


def main(argv: Sequence[str] | None = None) -> int:
    """Parse argv, run the subcommand, return a process exit code."""
    args = build_parser().parse_args(argv)
    try:
        spec = load_spec(args.spec)
        provider = _make_provider(args.provider, args.clouds_yaml, args.cloud)

        if args.command == "plan":
            decision = plan(spec, provider)
            print(f"{spec.name}: {decision.action.value.upper()} — {decision.reason}")
            return _EXIT_OK

        if not args.yes:
            decision = plan(spec, provider)
            print(f"{spec.name}: would {args.command} ({decision.action.value}); pass --yes to do it")
            return _EXIT_OK

        if args.command == "apply":
            result = apply(spec, provider, allow_replace=args.allow_replace)
            ips = ", ".join(result.ip_addresses) or "(pending)"
            print(f"{result.name}: {result.action.value.upper()} id={result.server_id} ips={ips}")
            return _EXIT_OK

        # destroy
        removed = destroy(spec.name, provider)
        print(f"{spec.name}: {'DESTROYED' if removed else 'absent (nothing to destroy)'}")
        return _EXIT_OK
    except SpecError as exc:
        print(f"spec error: {exc}", file=sys.stderr)
        return _EXIT_USAGE_ERR
    except ProvisionError as exc:
        print(f"provision error: {exc}", file=sys.stderr)
        return _EXIT_PROVISION_ERR


if __name__ == "__main__":
    raise SystemExit(main())
