

import re


POSTGRES_RESERVED_WORDS = {
    "ALL",
    "ANALYSE",
    "ANALYZE",
    "AND",
    "ANY",
    "ARRAY",
    "AS",
    "ASC",
    "ASYMMETRIC",
    "AUTHORIZATION",
    "BINARY",
    "BOTH",
    "CASE",
    "CAST",
    "CHECK",
    "COLLATE",
    "COLUMN",
    "CONSTRAINT",
    "CREATE",
    "CROSS",
    "CURRENT_CATALOG",
    "CURRENT_DATE",
    "CURRENT_ROLE",
    "CURRENT_SCHEMA",
    "CURRENT_TIME",
    "CURRENT_TIMESTAMP",
    "CURRENT_USER",
    "DEFAULT",
    "DEFERRABLE",
    "DESC",
    "DISTINCT",
    "DO",
    "ELSE",
    "END",
    "EXCEPT",
    "FALSE",
    "FETCH",
    "FOR",
    "FOREIGN",
    "FREEZE",
    "FROM",
    "FULL",
    "GRANT",
    "GROUP",
    "HAVING",
    "ILIKE",
    "IN",
    "INITIALLY",
    "INNER",
    "INTERSECT",
    "INTO",
    "IS",
    "ISNULL",
    "JOIN",
    "LATERAL",
    "LEADING",
    "LEFT",
    "LIKE",
    "LIMIT",
    "LOCALTIME",
    "LOCALTIMESTAMP",
    "NATURAL",
    "NOT",
    "NOTNULL",
    "NULL",
    "OFFSET",
    "ON",
    "ONLY",
    "OR",
    "ORDER",
    "OUTER",
    "OVERLAPS",
    "PLACING",
    "PRIMARY",
    "REFERENCES",
    "RETURNING",
    "RIGHT",
    "SELECT",
    "SESSION_USER",
    "SIMILAR",
    "SOME",
    "SYMMETRIC",
    "TABLE",
    "THEN",
    "TO",
    "TRAILING",
    "TRUE",
    "UNION",
    "UNIQUE",
    "USER",
    "USING",
    "VARIADIC",
    "VERBOSE",
    "WHEN",
    "WHERE",
    "WINDOW",
    "WITH",
}


def to_pg_identifier(identifier: str) -> str:
    """
    Makes sure a string isn't a pg reserved word and converts it to snake_case.
    Throws an exception if the identifier is a Postgres reserved word
    :identifier: A string in pascal or camel case
    :rtype: str
    """
    """
    Convert PascalCase or camelCase to lower_snake_case.

    Examples:
        "PascalCase"      -> "pascal_case"
        "camelCase"       -> "camel_case"
        "HTTPRequest"     -> "http_request"
        "JSONDataParser"  -> "json_data_parser"
        "myURLValue"      -> "my_url_value"
    """
    POSTGRES_IDENTIFIER_LENGTH_LIMIT = 63
    # Handle acronym-to-word boundaries (JSONData -> JSON_Data)
    s1 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", identifier)

    # Handle lower/number to upper (camelCase, myURLValue)
    s2 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1)

    valid_identifier = s2.lower()
    if valid_identifier.upper() in POSTGRES_RESERVED_WORDS:
        raise RuntimeError("Class name must not be a Postgres reserved word")
    if len(valid_identifier) > POSTGRES_IDENTIFIER_LENGTH_LIMIT:
        raise RuntimeError(f"Class name exceeds {POSTGRES_IDENTIFIER_LENGTH_LIMIT} characters. Class {identifier} is invalid.")
    return valid_identifier
