import logging
from typing import Any, Dict

import pandas as pd

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def infer_schema(df: pd.DataFrame) -> Dict[str, Any]:
    """Infer JSON schema from DataFrame columns."""
    schema = {"type": "object", "properties": {}, "required": []}

    try:
        for col in df.columns:
            if df[col].isnull().all():
                continue

            col_data = df[col].dropna()
            example = col_data.sample(1).iloc[0]
            col_schema = infer_column_schema(col_data, example)

            if col_schema:
                schema["properties"][col] = col_schema
                schema["required"].append(col)

        return schema

    except Exception as e:
        logger.error(f"Error inferring schema: {e}")
        return schema


def infer_column_schema(col_data: pd.Series, example: Any) -> Dict[str, Any]:
    """Infer schema for a single column."""
    try:
        if pd.api.types.is_numeric_dtype(col_data):
            return infer_numeric_schema(col_data, example)
        elif pd.api.types.is_string_dtype(col_data):
            return infer_string_schema(col_data, example)
        else:
            return {"type": "string", "example": str(example)}

    except Exception as e:
        logger.error(f"Error inferring column schema: {e}")
        return None


def infer_numeric_schema(col_data: pd.Series, example: Any) -> Dict[str, Any]:
    try:
        min_val = col_data.min()
        max_val = col_data.max()

        col_type = "integer" if pd.api.types.is_integer_dtype(col_data) else "number"

        return {
            "type": col_type,
            "description": f"Typical value between {min_val} and {max_val}.",
            "example": example.item(),
        }

    except Exception as e:
        logger.error(f"Error inferring numeric schema: {e}")
        return {"type": "number", "example": float(example)}


def infer_string_schema(col_data: pd.Series, example: str) -> Dict[str, Any]:
    try:
        unique_vals = col_data.unique().tolist()
        if 1 < len(unique_vals) <= 20:
            return {"type": "string", "enum": unique_vals}

        return {"type": "string", "example": str(example)}

    except Exception as e:
        logger.error(f"Error inferring string schema: {e}")
        return {"type": "string", "example": str(example)}


def validate_against_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> bool:
    try:
        for field in schema.get("required", []):
            if field not in data:
                logger.error(f"Missing required field: {field}")
                return False

        for field, value in data.items():
            field_schema = schema.get("properties", {}).get(field)
            if not field_schema:
                continue

            if field_schema["type"] == "number":
                if not isinstance(value, (int, float)):
                    logger.error(f"Invalid type for field {field}: expected number")
                    return False
            elif field_schema["type"] == "integer":
                if not isinstance(value, int):
                    logger.error(f"Invalid type for field {field}: expected integer")
                    return False
            elif field_schema["type"] == "string":
                if not isinstance(value, str):
                    logger.error(f"Invalid type for field {field}: expected string")
                    return False

            if "enum" in field_schema and value not in field_schema["enum"]:
                logger.error(f"Invalid value for field {field}: must be one of {field_schema['enum']}")
                return False

        return True

    except Exception as e:
        logger.error(f"Error validating against schema: {e}")
        return False
