from typing import Union, Dict, List, Any, cast

from coinmetrics._schema_base import ListDataType, ObjectDataType, DataType, DECIMAL, DATE_TIME, STRING, SchemaWrapper
from coinmetrics._schema_gen import ENDPOINTS, DYNAMIC_SCHEMA_BUILDERS, ENDPOINT_PATH_PATTERNS


LIST_TEXT_METRICS = [
    "MinerEntity",
    "principal_market_usd",
]


def get_schema(endpoint: str, params: Dict[str, Any] = {}) -> SchemaWrapper:
    if not endpoint.startswith("/"):
        endpoint = "/" + endpoint
    schema = ENDPOINTS.get(endpoint)
    if schema is None:
        for path_param_endpoint, path_pattern in ENDPOINT_PATH_PATTERNS.items():
            match = path_pattern.match(endpoint)
            if match is not None:
                endpoint = path_param_endpoint
                schema = ENDPOINTS[endpoint]
                params = {**params, **match.groupdict()}
                break
    if schema is None:
        raise Exception(f"No schema found for: {endpoint}")
    if endpoint in DYNAMIC_SCHEMA_BUILDERS:
        dynamic_schema_builder = DYNAMIC_SCHEMA_BUILDERS[endpoint]
        if dynamic_schema_builder is None:
            raise Exception(f"No dynamic schema builder defined for: {endpoint}")
        schema = dynamic_schema_builder(endpoint, params, cast(ListDataType, schema))

    # Wrap schema with error handling
    return SchemaWrapper(cast(Union[ListDataType, ObjectDataType], schema), endpoint)


def _build_metrics_endpoint_schema(
    _endpoint: str,
    params: Dict[str, Union[str | List[str]]],
    schema: ListDataType,
) -> Union[ListDataType | ObjectDataType]:
    metrics = params.get("metrics")
    if metrics is None:
        return schema
    if isinstance(metrics, str):
        metrics = metrics.split(",")
    elif isinstance(metrics, (list, tuple)):
        metrics = list(metrics)
    else:
        # For other types (int, date, etc.), convert to string and split
        metrics = [str(metrics)]

    assert isinstance(schema.item, ObjectDataType)
    items = schema.item.items

    for metric in metrics:
        if metric in LIST_TEXT_METRICS:
            data_type: DataType = STRING
        else:
            data_type = DECIMAL
        items[metric] = data_type.nullable()

        # Handle special metrics that add status fields
        if metric.startswith(("Sply", "Flow", "TxEx")):
            items[f"{metric}-status-time"] = DATE_TIME.nullable()
            items[f"{metric}-status"] = STRING.nullable()

    return ListDataType(ObjectDataType(items))


DYNAMIC_SCHEMA_BUILDERS["/constituent-snapshots/asset-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/constituent-timeframes/asset-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/asset-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/exchange-asset-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/exchange-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/institution-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/market-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/pair-metrics"] = _build_metrics_endpoint_schema
DYNAMIC_SCHEMA_BUILDERS["/timeseries/exchange-pair-metrics"] = _build_metrics_endpoint_schema
