from __future__ import annotations

import os
from typing import Optional

from azure.identity import DefaultAzureCredential
from redis.asyncio import Redis
from redis_entraid.cred_provider import create_from_default_azure_credential


class InMemoryRedis:
    """Fallback in-memory Redis implementation for development."""
    def __init__(self) -> None:
        self._store: dict[str, str] = {}

    def pipeline(self) -> "InMemoryPipeline":
        return InMemoryPipeline(self)

    async def ping(self) -> bool:
        return True

    async def set(self, key: str, value: str, ex: int | None = None, nx: bool = False) -> bool:
        if nx and key in self._store:
            return False
        self._store[key] = value
        return True

    async def incr(self, key: str) -> int:
        current = int(self._store.get(key, "0"))
        current += 1
        self._store[key] = str(current)
        return current

    async def decr(self, key: str) -> int:
        current = int(self._store.get(key, "0"))
        current -= 1
        self._store[key] = str(current)
        return current

    async def incrbyfloat(self, key: str, amount: float) -> float:
        current = float(self._store.get(key, "0"))
        current += amount
        self._store[key] = str(current)
        return current

    async def delete(self, key: str) -> int:
        existed = 1 if key in self._store else 0
        self._store.pop(key, None)
        return existed

    async def expire(self, key: str, seconds: int) -> None:
        _ = key
        _ = seconds
        return

    async def close(self) -> None:
        return


class InMemoryPipeline:
    """Minimal async pipeline for InMemoryRedis."""

    def __init__(self, redis: InMemoryRedis) -> None:
        self._redis = redis
        self._commands: list[tuple[str, tuple, dict]] = []

    def get(self, key: str) -> "InMemoryPipeline":
        self._commands.append(("get", (key,), {}))
        return self

    def set(self, key: str, value: str, ex: int | None = None, nx: bool = False) -> "InMemoryPipeline":
        self._commands.append(("set", (key, value), {"ex": ex, "nx": nx}))
        return self

    def incr(self, key: str) -> "InMemoryPipeline":
        self._commands.append(("incr", (key,), {}))
        return self

    def decr(self, key: str) -> "InMemoryPipeline":
        self._commands.append(("decr", (key,), {}))
        return self

    def incrbyfloat(self, key: str, amount: float) -> "InMemoryPipeline":
        self._commands.append(("incrbyfloat", (key, amount), {}))
        return self

    def expire(self, key: str, seconds: int) -> "InMemoryPipeline":
        self._commands.append(("expire", (key, seconds), {}))
        return self

    async def execute(self) -> list:
        results = []
        for command, args, kwargs in self._commands:
            method = getattr(self._redis, command)
            results.append(await method(*args, **kwargs))
        self._commands = []
        return results


_client: Optional[Redis | InMemoryRedis] = None


def _get_azure_redis_config() -> tuple[str, int]:
    """Get Azure Redis host and port from environment."""
    host = os.getenv("AZURE_REDIS_HOST", "")
    port = int(os.getenv("AZURE_REDIS_PORT", "10000"))
    return host, port


def _get_fallback_redis_url() -> str:
    """Get fallback Redis URL for local development."""
    return os.getenv("REDIS_URL", "redis://localhost:6379/0")


async def get_redis() -> Redis | InMemoryRedis:
    """Get Redis client with Azure Entra ID authentication."""
    global _client
    if _client is None:
        redis_host, redis_port = _get_azure_redis_config()
        
        if redis_host:
            # Try Azure Managed Redis with Entra ID authentication
            try:
                credential_provider = create_from_default_azure_credential(
                    ("https://redis.azure.com/.default",),
                )
                
                client = Redis(
                    host=redis_host,
                    port=redis_port,
                    ssl=True,
                    decode_responses=True,
                    credential_provider=credential_provider,
                    socket_timeout=5,
                    socket_connect_timeout=5,
                    health_check_interval=30
                )
                
                # Test connection
                await client.ping()
                _client = client
                return _client
                
            except Exception as e:
                # Fall through to try fallback URL / in-memory
                _ = e
        
        # Try fallback Redis URL (local development)
        fallback_url = _get_fallback_redis_url()
        if fallback_url and "localhost" in fallback_url:
            try:
                client = Redis.from_url(
                    fallback_url,
                    decode_responses=True,
                    socket_timeout=1,
                    socket_connect_timeout=1,
                    health_check_interval=30,
                )
                await client.ping()
                _client = client
                return _client
            except Exception as e:
                _ = e
        
        # Final fallback to in-memory Redis
        _client = InMemoryRedis()
    
    return _client


async def close_redis() -> None:
    """Close Redis connection."""
    global _client
    if _client is not None:
        await _client.close()
        _client = None
