"""
Redis Metadata Store Implementation

Contract-centric store keyed by (contract_name, contract_version).
Keys: contract:{name}:{version}:schema|metadata|coercion_rules|validation_rules
Index: contracts:index = set of "name:version"
"""

from __future__ import annotations

import json
from typing import Any

try:
    import redis  # type: ignore[import-not-found,import-untyped]

    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False
    redis = None

from pycharter.metadata_store.client import MetadataStoreClient


class RedisMetadataStore(MetadataStoreClient):
    """
    Redis metadata store keyed by (contract_name, contract_version).
    Key patterns: contract:{name}:{version}:schema, :metadata, :coercion_rules, :validation_rules
    Index: contracts:index = set of "name:version"
    """

    def __init__(
        self,
        connection_string: str | None = None,
        key_prefix: str = "pycharter",
    ):
        if not REDIS_AVAILABLE:
            raise ImportError(
                "redis is required for RedisMetadataStore. Install with: pip install redis"
            )
        super().__init__(connection_string)
        self.key_prefix = key_prefix
        self._client: redis.Redis | None = None

    def connect(self) -> None:
        if not self.connection_string:
            raise ValueError("connection_string is required for Redis")
        self._client = redis.from_url(self.connection_string, decode_responses=True)
        self._client.ping()
        self._connection = self._client

    def disconnect(self) -> None:
        if self._client:
            self._client.close()
            self._client = None
            self._connection = None

    def _key(self, *parts: str) -> str:
        return f"{self.key_prefix}:{':'.join(parts)}"

    def _contract_key(
        self, contract_name: str, contract_version: str, artifact: str
    ) -> str:
        return self._key("contract", contract_name, contract_version, artifact)

    def _contract_index_key(self) -> str:
        return self._key("contracts", "index")

    def _add_to_index(self, contract_name: str, contract_version: str) -> None:
        self._require_connection()
        self._client.sadd(
            self._contract_index_key(),
            f"{contract_name}:{contract_version}",
        )

    def store_schema(
        self,
        contract_name: str,
        contract_version: str,
        schema: dict[str, Any],
    ) -> str:
        from pycharter.shared.name_validator import validate_schema_table_names

        self._require_connection()
        schema = dict(schema)
        if "version" not in schema:
            schema["version"] = contract_version
        validate_schema_table_names(schema)
        key = self._contract_key(contract_name, contract_version, "schema")
        self._client.set(key, json.dumps(schema))
        self._add_to_index(contract_name, contract_version)
        return f"{contract_name}:{contract_version}"

    def get_schema(
        self, contract_name: str, contract_version: str
    ) -> dict[str, Any] | None:
        self._require_connection()
        data = self._client.get(
            self._contract_key(contract_name, contract_version, "schema")
        )
        if not data:
            return None
        schema = json.loads(data)
        if "version" not in schema:
            schema["version"] = contract_version
        return schema

    def list_contracts(self) -> list[dict[str, Any]]:
        self._require_connection()
        members = self._client.smembers(self._contract_index_key())
        result = []
        for m in members:
            if ":" in m:
                name, version = m.split(":", 1)
                result.append({"name": name, "version": version})
        return result

    def store_metadata(
        self,
        contract_name: str,
        contract_version: str,
        metadata: dict[str, Any],
    ) -> str:
        self._require_connection()
        meta = dict(metadata)
        meta["version"] = contract_version
        key = self._contract_key(contract_name, contract_version, "metadata")
        self._client.set(key, json.dumps(meta))
        self._add_to_index(contract_name, contract_version)
        return f"{contract_name}:{contract_version}:metadata"

    def get_metadata(
        self, contract_name: str, contract_version: str
    ) -> dict[str, Any] | None:
        self._require_connection()
        data = self._client.get(
            self._contract_key(contract_name, contract_version, "metadata")
        )
        return json.loads(data) if data else None

    def store_coercion_rules(
        self,
        contract_name: str,
        contract_version: str,
        coercion_rules: dict[str, Any],
    ) -> str:
        self._require_connection()
        key = self._contract_key(contract_name, contract_version, "coercion_rules")
        self._client.set(key, json.dumps(coercion_rules))
        self._add_to_index(contract_name, contract_version)
        return f"{contract_name}:{contract_version}:coercion"

    def get_coercion_rules(
        self, contract_name: str, contract_version: str
    ) -> dict[str, Any] | None:
        self._require_connection()
        data = self._client.get(
            self._contract_key(contract_name, contract_version, "coercion_rules")
        )
        return json.loads(data) if data else None

    def store_validation_rules(
        self,
        contract_name: str,
        contract_version: str,
        validation_rules: dict[str, Any],
    ) -> str:
        self._require_connection()
        key = self._contract_key(contract_name, contract_version, "validation_rules")
        self._client.set(key, json.dumps(validation_rules))
        self._add_to_index(contract_name, contract_version)
        return f"{contract_name}:{contract_version}:validation"

    def get_validation_rules(
        self, contract_name: str, contract_version: str
    ) -> dict[str, Any] | None:
        self._require_connection()
        data = self._client.get(
            self._contract_key(contract_name, contract_version, "validation_rules")
        )
        return json.loads(data) if data else None
