"""Stack discovery helpers for Quilt catalogs."""

from __future__ import annotations

import json
import urllib.request
from pathlib import Path
from typing import Any, Callable, Iterable, Mapping
from urllib.parse import urlparse

from platformdirs import user_data_path

from quiltx._version import __version__
from quiltx.utils import get_hostname, normalize_url


def extract_catalog_name(config: Mapping[str, Any]) -> str:
    catalog_name = config.get("catalog")
    if catalog_name:
        return str(catalog_name)

    navigator_url = config.get("navigator_url")
    if not navigator_url:
        raise ValueError("navigator_url not set in Quilt config")

    parsed = urlparse(str(navigator_url))
    if not parsed.hostname:
        raise ValueError(f"Invalid navigator_url: {navigator_url}")

    return parsed.hostname


def fetch_catalog_config(
    catalog_url: str, opener: Callable[[str], Any] = urllib.request.urlopen
) -> Mapping[str, Any]:
    config_url = catalog_url.rstrip("/") + "/config.json"
    with opener(config_url) as response:
        return json.load(response)


def resolve_region(config: Mapping[str, Any], catalog_config: Mapping[str, Any]) -> str:
    region = catalog_config.get("region") or config.get("region")
    if not region:
        raise ValueError("No region found in catalog config or local config")
    return str(region)


def stack_outputs(stack: Mapping[str, Any]) -> Iterable[Mapping[str, Any]]:
    return stack.get("Outputs") or []


def stack_parameters(stack: Mapping[str, Any]) -> Iterable[Mapping[str, Any]]:
    return stack.get("Parameters") or []


def find_matching_stack(
    catalog_url: str,
    region: str | None = None,
    cfn_client: Any = None,
) -> Mapping[str, Any]:
    """Find CloudFormation stack matching the catalog URL.

    Args:
        catalog_url: Catalog URL to match (e.g., "https://example.quiltdata.com")
        region: AWS region (defaults to fetching from catalog config if not provided)
        cfn_client: Optional CloudFormation client (creates one if not provided)

    Returns:
        Stack information dictionary

    Raises:
        ValueError: If no matching stack is found
    """
    # Auto-detect region from catalog config if not provided
    if region is None and cfn_client is None:
        try:
            catalog_config = fetch_catalog_config(catalog_url)
            region = catalog_config.get("region")
            if not region:
                raise ValueError(
                    f"No region found in catalog config for {catalog_url}. "
                    "Please provide region parameter explicitly."
                )
        except Exception as exc:
            raise ValueError(
                f"Could not auto-detect region for {catalog_url}: {exc}. "
                "Please provide region parameter explicitly."
            ) from exc

    # Create CloudFormation client if not provided
    if cfn_client is None:
        import boto3

        cfn_client = boto3.client("cloudformation", region_name=region)

    expected_host = get_hostname(catalog_url)
    paginator = cfn_client.get_paginator("describe_stacks")

    output_host_matches = []

    for page in paginator.paginate():
        for stack in page.get("Stacks", []):
            for output in stack_outputs(stack):
                output_key = str(output.get("OutputKey", "")).lower()
                output_value = output.get("OutputValue")
                if not output_value:
                    continue
                if output_key == "quiltwebhost":
                    if get_hostname(str(output_value)) == expected_host:
                        output_host_matches.append(stack)

    if output_host_matches:
        return output_host_matches[0]

    raise ValueError("No stack found with QuiltWebHost matching " f"{catalog_url}")


def list_log_group_resources(
    stack_name: str,
    region: str | None = None,
    cfn_client: Any = None,
) -> list[dict[str, str]]:
    """List CloudWatch log groups in a CloudFormation stack.

    Args:
        stack_name: CloudFormation stack name
        region: AWS region (required if cfn_client not provided)
        cfn_client: Optional CloudFormation client (creates one if not provided)

    Returns:
        List of log group resource dictionaries
    """
    if cfn_client is None:
        if region is None:
            raise ValueError("Either region or cfn_client must be provided")
        import boto3

        cfn_client = boto3.client("cloudformation", region_name=region)

    paginator = cfn_client.get_paginator("list_stack_resources")
    log_groups = []

    for page in paginator.paginate(StackName=stack_name):
        for resource in page.get("StackResourceSummaries", []):
            if resource.get("ResourceType") != "AWS::Logs::LogGroup":
                continue
            log_groups.append(
                {
                    "logical_id": resource.get("LogicalResourceId", ""),
                    "log_group_name": resource.get("PhysicalResourceId", ""),
                }
            )

    return log_groups


def list_ecs_resources(
    stack_name: str,
    region: str | None = None,
    cfn_client: Any = None,
) -> list[dict[str, str]]:
    """List ECS resources in a CloudFormation stack.

    Args:
        stack_name: CloudFormation stack name
        region: AWS region (required if cfn_client not provided)
        cfn_client: Optional CloudFormation client (creates one if not provided)

    Returns:
        List of ECS resource dictionaries
    """
    if cfn_client is None:
        if region is None:
            raise ValueError("Either region or cfn_client must be provided")
        import boto3

        cfn_client = boto3.client("cloudformation", region_name=region)

    paginator = cfn_client.get_paginator("list_stack_resources")
    ecs_resources: list[dict[str, str]] = []

    ecs_types = {
        "AWS::ECS::Cluster",
        "AWS::ECS::Service",
        "AWS::ECS::TaskDefinition",
    }

    for page in paginator.paginate(StackName=stack_name):
        for resource in page.get("StackResourceSummaries", []):
            resource_type = resource.get("ResourceType")
            if resource_type not in ecs_types:
                continue
            ecs_resources.append(
                {
                    "logical_id": resource.get("LogicalResourceId", ""),
                    "physical_id": resource.get("PhysicalResourceId", ""),
                    "resource_type": resource_type or "",
                }
            )

    return ecs_resources


def stack_account_id(stack: Mapping[str, Any]) -> str | None:
    stack_id = stack.get("StackId")
    if not stack_id:
        return None
    parts = str(stack_id).split(":")
    if len(parts) >= 5:
        return parts[4]
    return None


def write_stack_payload(
    catalog_name: str,
    catalog_url: str,
    region: str,
    stack: Mapping[str, Any],
    log_groups: list[dict[str, str]],
    ecs_resources: list[dict[str, str]] | None = None,
    catalog_config: Mapping[str, Any] | None = None,
) -> Path:
    target_dir = user_data_path("quiltx") / catalog_name
    target_dir.mkdir(parents=True, exist_ok=True)
    output_path = target_dir / "stack.json"

    payload = {
        "catalog_name": catalog_name,
        "catalog_url": catalog_url,
        "web_url": catalog_url,
        "region": region,
        "account_id": stack_account_id(stack),
        "stack_name": stack.get("StackName"),
        "stack_id": stack.get("StackId"),
        "outputs": stack.get("Outputs") or [],
        "parameters": stack.get("Parameters") or [],
        "log_groups": log_groups,
        "ecs_resources": ecs_resources or [],
        "catalog_config": catalog_config or {},
        "quiltx_version": __version__,
    }

    output_path.write_text(json.dumps(payload, indent=2, sort_keys=True))
    return output_path


def load_stack_payload(catalog_name: str) -> Mapping[str, Any] | None:
    """Load stack payload for a catalog."""
    target_dir = user_data_path("quiltx") / catalog_name
    output_path = target_dir / "stack.json"
    if not output_path.exists():
        return None
    return json.loads(output_path.read_text())


def ensure_min_version(payload: Mapping[str, Any] | None, min_version: str) -> bool:
    """Check if payload was created by a version >= min_version.

    Returns True if payload has required version or higher.
    Returns False if payload is None, missing quiltx_version, or has lower version.

    Tools should check this and prompt user to run 'quiltx stack' if False.

    Example:
        payload = load_stack_payload(catalog_name)
        if not ensure_min_version(payload, "0.1.3"):
            print("Stack data outdated. Run 'quiltx stack' to refresh.")
            return 1
    """
    if not payload:
        return False
    payload_version = payload.get("quiltx_version")
    if not payload_version:
        return False  # Old payload without version field

    # Simple version comparison (works for semantic versions like "0.1.3")
    def parse_version(v: str) -> tuple[int, ...]:
        return tuple(int(x) for x in str(v).split("."))

    try:
        return parse_version(payload_version) >= parse_version(min_version)
    except (ValueError, AttributeError):
        return False
