"""Lightweight SQL dialect post-processing utilities."""

import re
from typing import Dict

_LIMIT_RE = re.compile(r"\bLIMIT\s+(\d+)\s*(;?)\s*$", re.IGNORECASE)


def _strip_semicolon(sql: str) -> tuple[str, str]:
    sql = sql.rstrip()
    if sql.endswith(";"):
        return sql[:-1], ";"
    return sql, ""


def sqlite_transform(sql: str) -> str:
    """Return the SQL unchanged."""
    return sql.strip()


def postgres_transform(sql: str) -> str:
    """Basic SQLite -> PostgreSQL transformations."""
    out = sql.strip()
    out = re.sub(r'`([^`]+)`', r'"\1"', out)
    out = re.sub(r"\bAUTOINCREMENT\b", "SERIAL", out, flags=re.IGNORECASE)
    out = re.sub(r"=\s*1\b", "= TRUE", out)
    out = re.sub(r"=\s*0\b", "= FALSE", out)
    return out


def mysql_transform(sql: str) -> str:
    """Basic SQLite -> MySQL transformations."""
    out = sql.strip()
    out = re.sub(r'"([A-Za-z0-9_]+)"', r'`\1`', out)
    out = re.sub(r"\bAUTOINCREMENT\b", "AUTO_INCREMENT", out, flags=re.IGNORECASE)
    out = re.sub(r"CURRENT_TIMESTAMP\s*\([^\)]*\)", "CURRENT_TIMESTAMP()", out, flags=re.IGNORECASE)
    return out


def mssql_transform(sql: str) -> str:
    """Basic SQLite -> T-SQL transformations."""
    out = sql.strip()
    out = re.sub(r'`([^`]+)`', r'[\1]', out)
    out = re.sub(r'"([A-Za-z0-9_]+)"', r'[\1]', out)
    out = re.sub(r"\bTRUE\b", "1", out, flags=re.IGNORECASE)
    out = re.sub(r"\bFALSE\b", "0", out, flags=re.IGNORECASE)
    out = re.sub(r"\bDATETIME\b", "DATETIME2", out, flags=re.IGNORECASE)
    out = re.sub(r"\bTIMESTAMP\b", "DATETIME2", out, flags=re.IGNORECASE)
    out = re.sub(r"\bINTEGER\s+PRIMARY\s+KEY\b", "INT PRIMARY KEY", out, flags=re.IGNORECASE)
    out = re.sub(r"\b(AUTOINCREMENT|AUTO_INCREMENT|SERIAL)\b", "IDENTITY(1,1)", out, flags=re.IGNORECASE)
    m = _LIMIT_RE.search(out)
    if m:
        n, semi = m.groups()
        out = _LIMIT_RE.sub("", out)
        if not re.search(r"(?i)\bTOP\s+\d+", out):
            if re.search(r"(?i)SELECT\s+DISTINCT", out):
                out = re.sub(r"(?i)SELECT\s+DISTINCT", f"SELECT DISTINCT TOP {n}", out, count=1)
            else:
                out = re.sub(r"(?i)SELECT", f"SELECT TOP {n}", out, count=1)
        out = out.rstrip()
        if semi:
            out += ";"
    out = re.sub(r"\s+;", ";", out)
    return out.strip()


_QUALIFY_RE = re.compile(r"\b(FROM|JOIN)\s+([^\s,;]+)", re.IGNORECASE)


def schema_qualify(
    sql: str,
    table_schemas: Dict[str, str],
    default_schema: str,
    normalize=lambda x: x,
) -> str:
    """Prefix unqualified table names using ``table_schemas``.

    Parameters
    ----------
    sql : str
        The SQL string to transform.
    table_schemas : Dict[str, str]
        Mapping of table name → schema name.
    default_schema : str
        Schema name that does not require qualification.

    Example
    -------
    >>> mapping = {"Products": "Sales"}
    >>> before = 'SELECT TOP 5 Product FROM Products ORDER BY Price DESC;'
    >>> schema_qualify(before, mapping, "dbo")
    'SELECT TOP 5 Product FROM Sales.Products ORDER BY Price DESC;'
    """

    def repl(match: re.Match) -> str:
        clause, table = match.groups()
        base = table
        if base.startswith("[") and base.endswith("]"):
            base = base[1:-1]
        if base.startswith("\"") and base.endswith("\""):
            base = base[1:-1]
        if "." in base or base.startswith("("):
            return match.group(0)
        key = normalize(base)
        schema = table_schemas.get(key)
        if schema and schema.lower() != default_schema.lower():
            return f"{clause} {schema}.{table}"
        return match.group(0)

    return _QUALIFY_RE.sub(repl, sql)


def schema_qualify_mssql(sql: str, table_schemas: Dict[str, str]) -> str:
    """Wrapper for MS SQL Server (default schema ``dbo``)."""
    mapping = {k: v for k, v in table_schemas.items()}
    return schema_qualify(sql, mapping, "dbo", normalize=lambda x: x)


def schema_qualify_postgres(sql: str, table_schemas: Dict[str, str]) -> str:
    """Wrapper for PostgreSQL (default schema ``public``)."""
    mapping = {k.lower(): v for k, v in table_schemas.items()}
    return schema_qualify(sql, mapping, "public", normalize=str.lower)


def schema_qualify_oracle(sql: str, table_schemas: Dict[str, str], default_schema: str) -> str:
    """Wrapper for Oracle."""
    mapping = {k.upper(): v for k, v in table_schemas.items()}
    return schema_qualify(sql, mapping, default_schema.upper(), normalize=str.upper)


def oracle_transform(sql: str) -> str:
    """Basic SQLite -> Oracle transformations."""
    out = sql.strip()
    out = re.sub(r'`([^`]+)`', r'"\1"', out)
    out = re.sub(r'\[([^\]]+)\]', r'"\1"', out)
    out = re.sub(r"\b(AUTOINCREMENT|AUTO_INCREMENT|SERIAL|IDENTITY\(1,1\))\b", "GENERATED BY DEFAULT AS IDENTITY", out, flags=re.IGNORECASE)
    out = re.sub(r"\bTRUE\b", "1", out, flags=re.IGNORECASE)
    out = re.sub(r"\bFALSE\b", "0", out, flags=re.IGNORECASE)
    out = re.sub(r"\bNOW\(\)\b", "SYSDATE", out, flags=re.IGNORECASE)
    m = _LIMIT_RE.search(out)
    if m:
        n, semi = m.groups()
        out = _LIMIT_RE.sub("", out).rstrip()
        out += f" FETCH FIRST {n} ROWS ONLY"
        if semi:
            out += ";"
    out = re.sub(r"\s+;", ";", out)
    return out.strip()


TRANSFORMS = {
    "sqlite": sqlite_transform,
    "postgres": postgres_transform,
    "postgresql": postgres_transform,
    "mysql": mysql_transform,
    "mssql": mssql_transform,
    "sqlserver": mssql_transform,
    "oracle": oracle_transform,
}


def transform(db_type: str, sql: str) -> str:
    """Return dialect-adjusted SQL for ``db_type``.

    Examples
    --------
    >>> transform("postgres", 'SELECT `id` FROM t LIMIT 1;')
    'SELECT "id" FROM t LIMIT 1;'
    >>> transform("mssql", 'SELECT * FROM t LIMIT 5;')
    'SELECT TOP 5 * FROM t;'
    """
    func = TRANSFORMS.get(db_type.lower())
    if func is None:
        return sql.strip()
    base, semi = _strip_semicolon(sql)
    try:
        out = func(base)
    except Exception:
        return sql.strip()
    out = out.rstrip()
    if semi:
        out += semi
    return out

