import re
from datetime import UTC, datetime
from typing import Annotated, Any, cast

from dateutil import parser
from eth_typing import ChecksumAddress, HexStr
from hexbytes import HexBytes
from pydantic import AfterValidator, BaseModel, BeforeValidator, ConfigDict, Field


def parse_flexible_datetime(v: str | datetime) -> datetime:
    """Parse datetime from multiple formats using dateutil."""
    if v in {"NOW*()", "NOW()"}:
        return datetime.fromtimestamp(0, tz=UTC)

    if isinstance(v, str):
        parsed = parser.parse(v)
        if not isinstance(parsed, datetime):
            msg = f"Failed to parse '{v}' as datetime, got {type(parsed)}"
            raise TypeError(msg)
        return parsed
    return v


# Validate format: 0x followed by 64 hex characters
KECCAK256_REGEX = re.compile(r"^0x[a-fA-F0-9]{64}$")


def validate_keccak256(v: str | HexBytes | bytes) -> str:
    """Validate and normalize Keccak256 hash format."""
    # Convert HexBytes/bytes to string
    if isinstance(v, HexBytes | bytes):
        v = v.hex()

    if not v.startswith("0x"):
        v = "0x" + v

    if not KECCAK256_REGEX.match(v):
        msg = f"Invalid Keccak256 hash format: {v}"
        raise ValueError(msg)

    return v


CHECKSUM_ADDRESS_REGEX = re.compile(r"^0x[a-fA-F0-9]{40}$")


def validate_eth_address(v: str | HexBytes | bytes) -> ChecksumAddress:
    """Validate and normalize Ethereum address format."""
    # Convert HexBytes/bytes to string
    if isinstance(v, HexBytes | bytes):
        v = v.hex()

    if not v.startswith("0x"):
        v = "0x" + v

    # Validate format: 0x followed by 40 hex characters
    if not CHECKSUM_ADDRESS_REGEX.match(v):
        msg = f"Invalid Ethereum address format: {v}"
        raise ValueError(msg)

    return cast("ChecksumAddress", v)


def hexbytes_to_str(v: HexBytes | bytes | str) -> HexStr:
    """Convert HexBytes to hex string with 0x prefix."""
    if isinstance(v, HexBytes):
        hex_str = v.hex()
        return cast("HexStr", hex_str if hex_str.startswith("0x") else f"0x{hex_str}")
    if isinstance(v, bytes):
        return cast("HexStr", "0x" + v.hex())
    if isinstance(v, str) and not v.startswith("0x"):
        return cast("HexStr", f"0x{v}")
    return cast("HexStr", v)


def validate_keccak_or_padded(v: Any) -> HexStr:
    """
    Validate Keccak256 or accept padded addresses (32 bytes with leading zeros).

    Some log topics are padded addresses, not proper Keccak256 hashes.
    """
    # First convert HexBytes/bytes to string with 0x prefix
    if isinstance(v, HexBytes | bytes):
        v = v.hex()

    # Ensure it's a string
    if not isinstance(v, str):
        msg = f"Expected string or bytes, got {type(v)}"
        raise TypeError(msg)

    # Add 0x prefix if missing
    v_str: HexStr = cast("HexStr", "0x" + v if not v.startswith("0x") else v)

    if KECCAK256_REGEX.match(v_str):
        return v_str

    msg = f"Invalid hash format: expected 66 characters (0x + 64 hex), got {len(v_str)}: {v_str}"
    raise ValueError(msg)


FlexibleDatetime = Annotated[datetime, BeforeValidator(parse_flexible_datetime)]
EthAddress = Annotated[ChecksumAddress, AfterValidator(validate_eth_address)]
Keccak256 = Annotated[str, AfterValidator(validate_keccak256)]
HexString = Annotated[str, BeforeValidator(hexbytes_to_str)]
Keccak256OrPadded = Annotated[str, BeforeValidator(validate_keccak_or_padded)]
EmptyString = Annotated[str, Field(pattern=r"^$", description="An empty string")]


class TimeseriesPoint(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    value: float = Field(alias="p")
    timestamp: datetime = Field(alias="t")
