from __future__ import annotations

from abc import ABC, abstractmethod
import re

from relationalai.semantics.std.datetime_format import DateFormatField, DateFormatLiteral, parse_julia_datetime_format

_TZ_PATTERN_KINDS = frozenset({"tz_offset", "tz_name"})
_DUCKDB_FORMAT_BY_KIND = {
    "year_2": "%y",
    "year_4": "%Y",
    "month_unpadded": "%-m",
    "month_padded": "%m",
    "day_unpadded": "%-d",
    "day_padded": "%d",
    "hour24_unpadded": "%-H",
    "hour24_padded": "%H",
    "minute_unpadded": "%-M",
    "minute_padded": "%M",
    "second_unpadded": "%-S",
    "second_padded": "%S",
    "millisecond": "%g",
    "weekday_abbr": "%a",
    "weekday_full": "%A",
    "month_abbr": "%b",
    "month_full": "%B",
    "tz_offset": "%z",
    "tz_name": "%Z",
    "hour12_unpadded": "%-I",
    "hour12_padded": "%I",
    "ampm": "%p",
}
_SNOWFLAKE_FORMAT_BY_KIND = {
    "year_2": "YY",
    "year_4": "YYYY",
    "month_unpadded": "MM",
    "month_padded": "MM",
    "day_unpadded": "DD",
    "day_padded": "DD",
    "hour24_unpadded": "HH24",
    "hour24_padded": "HH24",
    "minute_unpadded": "MI",
    "minute_padded": "MI",
    "second_unpadded": "SS",
    "second_padded": "SS",
    "millisecond": "FF3",
    "weekday_abbr": "DY",
    "weekday_full": "DAY",
    "month_abbr": "MON",
    "month_full": "MONTH",
    "tz_offset": "TZH:TZM",
    "tz_name": "TZD",
    "hour12_unpadded": "HH12",
    "hour12_padded": "HH12",
    "ampm": "AM",
}
_DATE_INVALID_FORMAT_KINDS = frozenset(
    {
        "second_unpadded",
        "second_padded",
        "millisecond",
        "hour24_unpadded",
        "hour24_padded",
        "minute_unpadded",
        "minute_padded",
        "tz_offset",
        "tz_name",
        "hour12_unpadded",
        "hour12_padded",
        "ampm",
    }
)


def _single_field_kind(fmt: str) -> str | None:
    parts = parse_julia_datetime_format(fmt)
    if len(parts) == 1 and isinstance(parts[0], DateFormatField):
        return parts[0].kind
    return None


def _translate_duckdb_format(fmt: str) -> tuple[str, set[str]]:
    parts = parse_julia_datetime_format(fmt)
    out: list[str] = []
    seen_kinds: set[str] = set()
    for part in parts:
        if isinstance(part, DateFormatLiteral):
            out.append(part.text.replace("%", "%%"))
            continue
        if isinstance(part, DateFormatField):
            seen_kinds.add(part.kind)
            mapped = _DUCKDB_FORMAT_BY_KIND.get(part.kind)
            if mapped is None:
                raise ValueError(f"Unsupported date format token kind {part.kind!r} in {fmt!r}")
            out.append(mapped)
            continue
        raise ValueError(f"Unknown date format part in {fmt!r}.")
    return "".join(out), seen_kinds


def _translate_snowflake_format(fmt: str) -> tuple[str, set[str]]:
    parts = parse_julia_datetime_format(fmt)
    out: list[str] = []
    seen_kinds: set[str] = set()
    for part in parts:
        if isinstance(part, DateFormatLiteral):
            escaped = part.text.replace("\\", "\\\\").replace('"', '""')
            out.append(f'"{escaped}"')
            continue
        if isinstance(part, DateFormatField):
            seen_kinds.add(part.kind)
            mapped = _SNOWFLAKE_FORMAT_BY_KIND.get(part.kind)
            if mapped is None:
                raise ValueError(f"Unsupported date format token kind {part.kind!r} in {fmt!r}")
            out.append(mapped)
            continue
        raise ValueError(f"Unknown date format part in {fmt!r}.")
    return "".join(out), seen_kinds


class SQLDialect(ABC):
    @property
    @abstractmethod
    def dialect_name(self) -> str:
        pass

    @abstractmethod
    def quote_identifier(self, name: str) -> str:
        pass

    @abstractmethod
    def format_table_name(self, name: str) -> str:
        pass


class DuckDBDialect(SQLDialect):
    @property
    def dialect_name(self) -> str:
        return "duckdb"

    def quote_identifier(self, name: str) -> str:
        return name

    def format_table_name(self, name: str) -> str:
        parts = name.split(".")
        if len(parts) == 3:
            db, schema, table = parts
            return f"{db}_{schema}.{table}"
        return name


class SnowflakeDialect(SQLDialect):
    _UNQUOTED_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_$]*$")
    # Conservative reserved-word set for object identifiers in Snowflake paths.
    _RESERVED_IDENTIFIERS = frozenset(
        {
            "ALL",
            "ALTER",
            "AND",
            "ANY",
            "AS",
            "BETWEEN",
            "BY",
            "CASE",
            "CAST",
            "CHECK",
            "COLUMN",
            "CONNECT",
            "CONSTRAINT",
            "CREATE",
            "CROSS",
            "CURRENT",
            "CURRENT_DATE",
            "CURRENT_TIME",
            "CURRENT_TIMESTAMP",
            "CURRENT_USER",
            "DATABASE",
            "DELETE",
            "DISTINCT",
            "DROP",
            "ELSE",
            "EXISTS",
            "FALSE",
            "FOLLOWING",
            "FOR",
            "FROM",
            "FULL",
            "GRANT",
            "GROUP",
            "HAVING",
            "ILIKE",
            "IN",
            "INCREMENT",
            "INNER",
            "INSERT",
            "INTERSECT",
            "INTO",
            "IS",
            "ISSUE",
            "JOIN",
            "LATERAL",
            "LEFT",
            "LIKE",
            "LOCALTIME",
            "LOCALTIMESTAMP",
            "MINUS",
            "NATURAL",
            "NOT",
            "NULL",
            "OF",
            "ON",
            "OR",
            "ORDER",
            "QUALIFY",
            "REGEXP",
            "REVOKE",
            "RIGHT",
            "RLIKE",
            "ROW",
            "ROWS",
            "SAMPLE",
            "SCHEMA",
            "SELECT",
            "SET",
            "SOME",
            "START",
            "TABLE",
            "TABLESAMPLE",
            "THEN",
            "TO",
            "TRIGGER",
            "TRUE",
            "TRY_CAST",
            "UNION",
            "UNIQUE",
            "UPDATE",
            "USING",
            "VALUES",
            "VIEW",
            "WHEN",
            "WHENEVER",
            "WHERE",
            "WITH",
        }
    )

    @property
    def dialect_name(self) -> str:
        return "snowflake"

    def quote_identifier(self, name: str) -> str:
        token = name.strip()
        if token == "*":
            return token
        if token.startswith('"') and token.endswith('"'):
            return token
        upper = token.upper()
        if self._UNQUOTED_IDENTIFIER_RE.fullmatch(token) and upper not in self._RESERVED_IDENTIFIERS:
            return upper
        escaped = token.replace('"', '""')
        return f'"{escaped}"'

    def format_table_name(self, name: str) -> str:
        return name


__all__ = [
    "DuckDBDialect",
    "SnowflakeDialect",
    "SQLDialect",
    "_DATE_INVALID_FORMAT_KINDS",
    "_TZ_PATTERN_KINDS",
    "_single_field_kind",
    "_translate_duckdb_format",
    "_translate_snowflake_format",
]
