"""Azure cloud resource state reader.

Reads live state of Azure resources using the Azure Management SDK.
Falls back to Azure CLI (`az resource show`) when SDK is unavailable.
"""

from __future__ import annotations

import json
import logging
import os
import subprocess
from dataclasses import dataclass, field
from typing import Any, Optional

logger = logging.getLogger(__name__)


@dataclass
class AzureResourceState:
    """Live state of an Azure resource."""
    resource_id: str
    name: str
    type: str
    location: str
    resource_group: str
    tags: dict[str, str] = field(default_factory=dict)
    properties: dict[str, Any] = field(default_factory=dict)
    sku: Optional[dict[str, Any]] = None
    kind: Optional[str] = None
    exists: bool = True


class AzureProvider:
    """Read live Azure resource state.

    Uses Azure Management SDK if available, falls back to Azure CLI.
    Authentication uses environment variables:
      - ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID, ARM_SUBSCRIPTION_ID
      - Or defaults to DefaultAzureCredential / Azure CLI login
    """

    def __init__(self, subscription_id: Optional[str] = None) -> None:
        self.subscription_id = subscription_id or os.environ.get("ARM_SUBSCRIPTION_ID", "")
        self._sdk_available = False
        self._client = None
        self._try_init_sdk()

    def _try_init_sdk(self) -> None:
        """Attempt to initialize Azure SDK client."""
        try:
            from azure.identity import DefaultAzureCredential, ClientSecretCredential
            from azure.mgmt.resource import ResourceManagementClient

            client_id = os.environ.get("ARM_CLIENT_ID")
            client_secret = os.environ.get("ARM_CLIENT_SECRET")
            tenant_id = os.environ.get("ARM_TENANT_ID")

            if client_id and client_secret and tenant_id:
                credential = ClientSecretCredential(tenant_id, client_id, client_secret)
            else:
                credential = DefaultAzureCredential()

            self._client = ResourceManagementClient(credential, self.subscription_id)
            self._sdk_available = True
            logger.info("Azure SDK initialized successfully")
        except ImportError:
            logger.info("Azure SDK not installed, will use CLI fallback")
        except Exception as e:
            logger.warning("Azure SDK init failed: %s, will use CLI fallback", e)

    def get_resource(self, resource_id: str) -> AzureResourceState:
        """Get the live state of an Azure resource by its resource ID.

        Args:
            resource_id: Full Azure resource ID.
                Example: /subscriptions/xxx/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sa

        Returns:
            AzureResourceState with current live properties.
        """
        if self._sdk_available:
            return self._get_via_sdk(resource_id)
        return self._get_via_cli(resource_id)

    def _get_via_sdk(self, resource_id: str) -> AzureResourceState:
        """Get resource state via Azure Management SDK."""
        try:
            # Parse resource ID components
            parts = self._parse_resource_id(resource_id)
            if not parts:
                return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                          resource_group="", exists=False)

            resource = self._client.resources.get_by_id(resource_id, api_version=parts.get("api_version", "2023-07-01"))
            return AzureResourceState(
                resource_id=resource.id,
                name=resource.name,
                type=resource.type,
                location=resource.location or "",
                resource_group=parts.get("resource_group", ""),
                tags=resource.tags or {},
                properties=resource.properties or {},
                sku={"name": resource.sku.name, "tier": resource.sku.tier} if resource.sku else None,
                kind=resource.kind,
                exists=True,
            )
        except Exception as e:
            logger.warning("SDK get failed for %s: %s", resource_id, e)
            if "ResourceNotFound" in str(e) or "NotFound" in str(e):
                return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                          resource_group="", exists=False)
            raise

    def _get_via_cli(self, resource_id: str) -> AzureResourceState:
        """Get resource state via Azure CLI (fallback)."""
        try:
            result = subprocess.run(
                ["az", "resource", "show", "--ids", resource_id, "-o", "json"],
                capture_output=True, text=True, timeout=30,
            )
            if result.returncode != 0:
                if "ResourceNotFound" in result.stderr or "could not be found" in result.stderr.lower():
                    return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                              resource_group="", exists=False)
                logger.warning("az CLI failed: %s", result.stderr)
                return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                          resource_group="", exists=False)

            data = json.loads(result.stdout)
            parts = self._parse_resource_id(resource_id)
            return AzureResourceState(
                resource_id=data.get("id", resource_id),
                name=data.get("name", ""),
                type=data.get("type", ""),
                location=data.get("location", ""),
                resource_group=parts.get("resource_group", ""),
                tags=data.get("tags", {}),
                properties=data.get("properties", {}),
                sku=data.get("sku"),
                kind=data.get("kind"),
                exists=True,
            )
        except FileNotFoundError:
            logger.error("Azure CLI (az) not found. Install it or use the Azure SDK.")
            return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                      resource_group="", exists=False)
        except subprocess.TimeoutExpired:
            logger.error("Azure CLI timed out for resource: %s", resource_id)
            return AzureResourceState(resource_id=resource_id, name="", type="", location="",
                                      resource_group="", exists=False)

    def list_resources_in_group(self, resource_group: str) -> list[AzureResourceState]:
        """List all resources in a resource group."""
        if self._sdk_available:
            try:
                resources = self._client.resources.list_by_resource_group(resource_group)
                return [
                    AzureResourceState(
                        resource_id=r.id,
                        name=r.name,
                        type=r.type,
                        location=r.location or "",
                        resource_group=resource_group,
                        tags=r.tags or {},
                        kind=r.kind,
                    )
                    for r in resources
                ]
            except Exception as e:
                logger.warning("SDK list failed: %s", e)

        # CLI fallback
        try:
            result = subprocess.run(
                ["az", "resource", "list", "-g", resource_group, "-o", "json"],
                capture_output=True, text=True, timeout=60,
            )
            if result.returncode == 0:
                data = json.loads(result.stdout)
                return [
                    AzureResourceState(
                        resource_id=r.get("id", ""),
                        name=r.get("name", ""),
                        type=r.get("type", ""),
                        location=r.get("location", ""),
                        resource_group=resource_group,
                        tags=r.get("tags", {}),
                        kind=r.get("kind"),
                    )
                    for r in data
                ]
        except Exception as e:
            logger.warning("CLI list failed: %s", e)
        return []

    @staticmethod
    def _parse_resource_id(resource_id: str) -> dict[str, str]:
        """Parse Azure resource ID into components."""
        parts: dict[str, str] = {}
        segments = resource_id.strip("/").split("/")
        for i in range(0, len(segments) - 1, 2):
            key = segments[i].lower()
            value = segments[i + 1] if i + 1 < len(segments) else ""
            if key == "subscriptions":
                parts["subscription_id"] = value
            elif key == "resourcegroups":
                parts["resource_group"] = value
            elif key == "providers":
                parts["provider_namespace"] = value

        # API version mapping based on resource type
        api_versions: dict[str, str] = {
            "Microsoft.Storage": "2023-01-01",
            "Microsoft.Compute": "2023-09-01",
            "Microsoft.Network": "2023-09-01",
            "Microsoft.Web": "2023-01-01",
            "Microsoft.Sql": "2023-05-01-preview",
            "Microsoft.KeyVault": "2023-07-01",
            "Microsoft.ContainerRegistry": "2023-07-01",
        }
        namespace = parts.get("provider_namespace", "")
        parts["api_version"] = api_versions.get(namespace, "2023-07-01")

        return parts

    def flatten_to_attributes(self, state: AzureResourceState) -> dict[str, Any]:
        """Flatten AzureResourceState to a flat attribute dict for comparison."""
        attrs: dict[str, Any] = {
            "name": state.name,
            "location": state.location,
            "resource_group_name": state.resource_group,
        }
        if state.tags:
            for k, v in state.tags.items():
                attrs[f"tags.{k}"] = v
        if state.sku:
            for k, v in state.sku.items():
                if v is not None:
                    attrs[f"sku.{k}"] = v
        if state.kind:
            attrs["kind"] = state.kind
        # Flatten properties
        if state.properties:
            for k, v in state.properties.items():
                if isinstance(v, (str, int, float, bool)):
                    attrs[f"properties.{k}"] = v
                elif isinstance(v, dict):
                    for k2, v2 in v.items():
                        if isinstance(v2, (str, int, float, bool)):
                            attrs[f"properties.{k}.{k2}"] = v2
        return attrs
