"""Apollo environment management tools: list, create, replicate, install_entity."""

from __future__ import annotations

import json

from cube_common.apollo_client import ApolloClient, ApolloError
from cube_common import apollo_queries as Q


async def _resolve_environment(client: ApolloClient, name_or_rid: str) -> dict:
    """Resolve an environment name or RID to {id, rid, name}.

    Raises ApolloError if not found or ambiguous.
    """
    if "ri." in name_or_rid:
        data = await client.graphql(Q.GET_ENVIRONMENT_BY_RID, {"rid": name_or_rid})
        env = data.get("apollo", {}).get("environment")
        if not env:
            raise ApolloError(f"Environment not found: {name_or_rid}")
        return env

    data = await client.graphql(Q.LIST_ENVIRONMENTS)
    envs = data.get("apollo", {}).get("environments", {}).get("environments", [])
    search = name_or_rid.lower()

    for env in envs:
        if env.get("name", "").lower() == search:
            return env

    matches = [e for e in envs if search in e.get("name", "").lower()]
    if len(matches) == 1:
        return matches[0]
    elif len(matches) == 0:
        raise ApolloError(
            f"No environment found matching '{name_or_rid}'. Use list_environments to see available names."
        )
    else:
        names = [f"  - {m['name']}" for m in matches[:10]]
        raise ApolloError(
            f"Multiple environments match '{name_or_rid}':\n" + "\n".join(names) + "\n\nPlease be more specific."
        )


async def list_environments(client: ApolloClient, search: str | None = None) -> str:
    data = await client.graphql(Q.LIST_ENVIRONMENTS)
    envs = data.get("apollo", {}).get("environments", {}).get("environments", [])

    if search:
        search_lower = search.lower()
        envs = [e for e in envs if search_lower in e.get("name", "").lower()]

    if not envs:
        suffix = f' matching "{search}"' if search else ""
        return f"No environments found{suffix}."

    lines = [f"Apollo Environments ({len(envs)})", "=" * 60]
    for env in sorted(envs, key=lambda e: e.get("name", "")):
        name = env.get("name", "unknown")
        rid = env.get("rid", "")
        ns = env.get("namespaceId", "")
        lines.append(f"  {name}")
        lines.append(f"    RID: {rid}")
        if ns:
            lines.append(f"    Namespace: {ns}")

    return "\n".join(lines)


async def create_environment(client: ApolloClient, name: str, accreditation: str = "DEV") -> str:
    data = await client.graphql(Q.CREATE_ENVIRONMENT, {
        "request": {
            "requestedId": name,
            "accreditation": accreditation.upper(),
        }
    })

    env = data.get("apollo", {}).get("createEnvironment", {})
    env_id = env.get("id", "")
    env_rid = env.get("rid", "")
    env_name = env.get("name", name)

    # Install Apollo Control Plane module
    install_vars = {
        "request": {
            "apolloEnvironmentId": env_id,
            "mavenCoordinate": "module:builtin-control-plane-minimal:0.0.40",
            "displayName": "Apollo Control Plane",
            "variables": [
                {
                    "variableName": "environmentId",
                    "variableValue": {"stringValue": name},
                }
            ],
            "options": [],
        }
    }

    lines = [
        "Environment Created",
        "=" * 50,
        f"  Name:          {env_name}",
        f"  ID:            {env_id}",
        f"  RID:           {env_rid}",
        f"  Space:         {env.get('spaceId', '')}",
        f"  Namespace:     {env.get('namespaceId', '')}",
        f"  Accreditation: {env.get('accreditation', '')}",
        "",
    ]

    try:
        module_data = await client.graphql(Q.INSTALL_MODULE, install_vars)
        module = module_data.get("apollo", {}).get("installModuleV2", {})
        state = module.get("state", {}).get("__typename", "").replace(
            "ApolloProductModuleInstallationStateV2_", ""
        )
        lines.append(f"Apollo Control Plane: {state}")
        lines.append(f"  Module: {module.get('moduleMavenCoordinate', '')}")
        lines.append(f"  RID:    {module.get('rid', '')}")
    except ApolloError as e:
        lines.append(f"Warning: Module install failed: {e}")
        lines.append("  Environment was created but Apollo Control Plane was not installed.")

    return "\n".join(lines)


async def replicate_environment(
    client: ApolloClient, source: str, target: str, accreditation: str = "DEV"
) -> str:
    lines: list[str] = []

    # Resolve source
    source_info = await _resolve_environment(client, source)
    source_id = source_info["id"]
    source_name = source_info.get("name", source)

    # Check if target exists
    try:
        target_info = await _resolve_environment(client, target)
    except ApolloError:
        target_info = None

    # Fetch source modules, entities, secrets
    source_data = await client.graphql(Q.SOURCE_ENVIRONMENT, {"id": source_id})
    source_env = source_data.get("apollo", {}).get("environmentById")
    if not source_env:
        return f"Source environment not found: {source_id}"

    source_modules = source_env.get("moduleInstallationsV2", {}).get("moduleInstallations", [])
    all_entities = source_env.get("entities", {}).get("entities", [])
    direct_entities = [e for e in all_entities if e.get("installedByModuleV2") is None]

    secrets_info = []
    for e in all_entities:
        for s in e.get("secrets", []):
            ename = e.get("entityLocator", {}).get("entityName", "?")
            keys = [k["key"] for k in (s.get("specification") or {}).get("keys", [])]
            secrets_info.append({"entity": ename, "name": s["name"], "keys": keys})

    lines.append(f"Replicating: {source_name} -> {target}")
    lines.append("=" * 60)
    lines.append(
        f"Source: {len(source_modules)} modules, {len(all_entities)} entities "
        f"({len(direct_entities)} direct), {len(secrets_info)} secrets"
    )
    lines.append("")

    # Step 1: Create or use existing target
    if target_info:
        target_id = target_info["id"]
        target_display = target_info.get("name", target)
        lines.append(f"1. Using existing environment: {target_display}")
        lines.append(f"   ID: {target_id}")
        lines.append(f"   RID: {target_info.get('rid', '')}")
    else:
        create_data = await client.graphql(Q.CREATE_ENVIRONMENT, {
            "request": {"requestedId": target, "accreditation": accreditation.upper()}
        })
        target_env = create_data.get("apollo", {}).get("createEnvironment", {})
        target_id = target_env.get("id", "")
        target_display = target_env.get("name", target)
        lines.append(f"1. Environment created: {target_display}")
        lines.append(f"   ID: {target_id}")
        lines.append(f"   RID: {target_env.get('rid', '')}")

    lines.append("")

    # Fetch target's existing modules/entities
    target_data = await client.graphql(Q.TARGET_ENVIRONMENT, {"id": target_id})
    target_env_data = target_data.get("apollo", {}).get("environmentById", {}) or {}

    existing_module_coords = {
        m["moduleMavenCoordinate"]
        for m in target_env_data.get("moduleInstallationsV2", {}).get("moduleInstallations", [])
    }
    existing_entity_names = {
        e.get("entityLocator", {}).get("entityName", "")
        for e in target_env_data.get("entities", {}).get("entities", [])
    }

    # Step 2: Install modules
    lines.append("2. Modules:")
    module_ok = module_skip = module_fail = 0
    for m in source_modules:
        coord = m["moduleMavenCoordinate"]
        display_name = m.get("displayName") or ""

        if coord in existing_module_coords:
            lines.append(f"   SKIP {display_name or coord} (already installed)")
            module_skip += 1
            continue

        var_inputs = []
        for v in m.get("variables", []):
            vtype = v["variableValue"]["__typename"]
            val = v["variableValue"]["value"]
            kind = "stringValue" if "String" in vtype else "optionValue"
            var_inputs.append({
                "variableName": v["variableName"],
                "variableValue": {kind: val},
            })

        request = {
            "apolloEnvironmentId": target_id,
            "mavenCoordinate": coord,
            "variables": var_inputs,
            "options": [],
        }
        if display_name:
            request["displayName"] = display_name

        try:
            data = await client.graphql(Q.INSTALL_MODULE, {"request": request})
            result = data.get("apollo", {}).get("installModuleV2", {})
            state = result.get("state", {}).get("__typename", "").replace(
                "ApolloProductModuleInstallationStateV2_", ""
            )
            lines.append(f"   OK   {display_name or coord} -> {state}")
            module_ok += 1
        except ApolloError as e:
            lines.append(f"   FAIL {display_name or coord}: {e}")
            module_fail += 1

    lines.append(f"   ({module_ok} installed, {module_skip} skipped, {module_fail} failed)")
    lines.append("")

    # Step 3: Install direct entities
    target_data2 = await client.graphql(Q.TARGET_ENVIRONMENT, {"id": target_id})
    target_env_data2 = (target_data2 or {}).get("apollo", {}).get("environmentById", {}) or {}
    existing_entity_names = {
        e.get("entityLocator", {}).get("entityName", "")
        for e in target_env_data2.get("entities", {}).get("entities", [])
    }

    new_direct = [
        e for e in direct_entities
        if e.get("entityLocator", {}).get("entityName", "") not in existing_entity_names
    ]
    skipped_direct = len(direct_entities) - len(new_direct)

    if new_direct:
        helm_charts = []
        for e in new_direct:
            locator = e.get("entityLocator", {})
            ename = locator.get("entityName", "")
            product_id = e.get("product", {}).get("productId", "")
            overrides = (e.get("reportedConfigOverrides") or {}).get("overrides")

            config_str = (
                f"0.0.0:\n  overrides: {json.dumps(overrides)}"
                if overrides
                else "0.0.0:\n  overrides: {}"
            )

            helm_charts.append({
                "helmChartName": ename,
                "productId": product_id,
                "k8sNamespace": "default",
                "entityInstallationOptionalInputs": {"configOverrides": config_str},
            })

        try:
            data = await client.graphql(Q.INSTALL_ENTITIES, {
                "envId": target_id,
                "entities": {"helmCharts": helm_charts},
            })
            cr = data.get("apollo", {}).get("installEntitiesOnEnvironment", {})
            lines.append(f"3. Direct entities ({len(helm_charts)} new, {skipped_direct} skipped):")
            lines.append(f"   Change request: {cr.get('requestStatus', '?')}")
            for hc in helm_charts:
                lines.append(f"   - {hc['helmChartName']} ({hc['productId']})")
        except ApolloError as e:
            lines.append(f"3. Direct entities ({len(helm_charts)} new, {skipped_direct} skipped):")
            lines.append(f"   FAIL: {e}")
    else:
        lines.append(f"3. Direct entities: {skipped_direct} already exist, nothing new to install.")

    lines.append("")

    # Step 4: Cleanup extra entities
    source_entity_names = {
        e.get("entityLocator", {}).get("entityName", "") for e in all_entities
    }

    cleanup_data = await client.graphql(Q.CLEANUP_CHECK, {"id": target_id})
    target_entities_now = (
        cleanup_data.get("apollo", {}).get("environmentById", {}).get("entities", {}).get("entities", [])
    )

    extras = [
        e for e in target_entities_now
        if e.get("entityLocator", {}).get("entityName", "") not in source_entity_names
    ]

    if extras:
        lines.append(f"4. Cleanup ({len(extras)} extra entities to uninstall):")
        uninstall_ok = uninstall_fail = 0
        for e in extras:
            ename = e.get("entityLocator", {}).get("entityName", "?")
            erid = e.get("rid", "")
            try:
                data = await client.graphql(Q.UNINSTALL_ENTITY, {"rid": erid})
                cr = data.get("apollo", {}).get("uninstallEntity", {})
                lines.append(f"   OK   {ename}: {cr.get('requestStatus', '?')}")
                uninstall_ok += 1
            except ApolloError as e_err:
                lines.append(f"   FAIL {ename}: {e_err}")
                uninstall_fail += 1
        lines.append(f"   ({uninstall_ok} uninstalled, {uninstall_fail} failed)")
    else:
        lines.append("4. Cleanup: no extra entities to remove.")

    lines.append("")

    # Step 5: Report missing entities
    final_data = await client.graphql(Q.CLEANUP_CHECK, {"id": target_id})
    final_entities = {
        e.get("entityLocator", {}).get("entityName", "")
        for e in final_data.get("apollo", {}).get("environmentById", {}).get("entities", {}).get("entities", [])
    }
    missing = source_entity_names - final_entities
    if missing:
        lines.append(f"5. Missing entities ({len(missing)}):")
        lines.append("   These entities could not be replicated (module install may have failed):")
        for n in sorted(missing):
            lines.append(f"   - {n}")
        lines.append("")

    # Step 6: Report secrets
    step = "6" if missing else "5"
    if secrets_info:
        lines.append(f"{step}. Secrets ({len(secrets_info)} found):")
        lines.append("   Secrets cannot be created until an agent connects to the environment.")
        lines.append("   The following secrets need to be configured manually:")
        for s in secrets_info:
            keys_str = ", ".join(s["keys"]) if s["keys"] else "(single value)"
            lines.append(f"   - {s['entity']}/{s['name']} [keys: {keys_str}]")
    else:
        lines.append(f"{step}. No secrets to replicate.")

    return "\n".join(lines)


async def install_entity(
    client: ApolloClient,
    environment: str,
    entity_name: str,
    product_id: str,
    k8s_namespace: str = "default",
    config_overrides: str | None = None,
) -> str:
    env_info = await _resolve_environment(client, environment)
    env_id = env_info["id"]

    optional: dict = {}
    if config_overrides:
        try:
            overrides = json.loads(config_overrides)
            optional["configOverrides"] = f"0.0.0:\n  overrides: {json.dumps(overrides)}"
        except json.JSONDecodeError:
            return f"Error: config_overrides must be valid JSON. Got: {config_overrides[:200]}"
    else:
        optional["configOverrides"] = "0.0.0:\n  overrides: {}"

    helm_chart = {
        "helmChartName": entity_name,
        "productId": product_id,
        "k8sNamespace": k8s_namespace,
        "entityInstallationOptionalInputs": optional,
    }

    data = await client.graphql(Q.INSTALL_ENTITIES, {
        "envId": env_id,
        "entities": {"helmCharts": [helm_chart]},
    })

    cr = data.get("apollo", {}).get("installEntitiesOnEnvironment", {})
    lines = [
        f"Entity installed on {env_info.get('name', environment)}",
        "=" * 50,
        f"  Entity:          {entity_name}",
        f"  Product:         {product_id}",
        f"  Namespace:       {k8s_namespace}",
        f"  Config:          {'custom overrides' if config_overrides else 'none'}",
        "",
        f"  Change Request:  {cr.get('requestStatus', '?')}",
        f"  CR RID:          {cr.get('rid', '')}",
    ]

    return "\n".join(lines)
