from __future__ import annotations

import re


class ParseError(ValueError):
    def __init__(self, identifier: str):
        super().__init__(
            "Could not parse fully-qualified name; make sure the identifier is "
            f"fully qualified and valid: {identifier}"
        )


class IdentityParser:
    """Port of the v0 Snowflake identifier parser with light v1 helpers."""

    SF_ID_REGEX = re.compile(r"^[A-Za-z_][A-Za-z0-9_$]*$")

    def __init__(
        self,
        identifier: str,
        require_all_parts: bool = False,
        force_upper_case: bool = True,
    ):
        self.identifier = str(identifier or "").strip()
        self.db_part: dict | None = None
        self.schema_part: dict | None = None
        self.entity_part: dict | None = None
        self.identity: str | None = None
        self._is_complete = False
        self.has_double_quoted_identifier = False
        self.require_all_parts = require_all_parts
        self.force_upper_case = force_upper_case
        self._parse()

    @property
    def db(self) -> str | None:
        return self.db_part["part"] if self.db_part else None

    @property
    def schema(self) -> str | None:
        return self.schema_part["part"] if self.schema_part else None

    @property
    def entity(self) -> str | None:
        return self.entity_part["part"] if self.entity_part else None

    @property
    def is_complete(self) -> bool:
        return self._is_complete

    def _parse(self) -> None:
        self.db_part = self._get_part(self.identifier)
        if self.db_part["next"]:
            self.schema_part = self._get_part(self.db_part["next"])
            if self.schema_part["next"]:
                self.entity_part = self._get_part(self.schema_part["next"])
                self._is_complete = True

        if self.require_all_parts and (not self.db or not self.schema or not self.entity):
            raise ParseError(self.identifier)

        normalized_parts = [
            self._normalize_id(part)
            for part in [self.db_part, self.schema_part, self.entity_part]
            if part
        ]
        self.identity = ".".join(part for part in normalized_parts if part is not None)
        self.has_double_quoted_identifier = '"' in self.identity if self.identity else False

    def _get_part(self, string: str) -> dict:
        if len(string) == 0:
            return {"part": None, "next": None}
        if string[0] == '"':
            return self._get_quoted_part(string)
        return self._get_normal_part(string)

    def _get_normal_part(self, string: str) -> dict:
        ret = ""
        i = 0
        for char in string:
            i += 1
            if char == ".":
                break
            ret += char
        if self.SF_ID_REGEX.match(ret) and self.force_upper_case:
            ret = ret.upper()
        return {"part": ret, "quoted": False, "next": string[i:]}

    def _get_quoted_part(self, string: str) -> dict:
        ret = ""
        i = 1
        while i < len(string):
            char = string[i]
            next_char = string[i + 1] if i + 1 < len(string) else None
            if char == '"':
                if next_char == ".":
                    i += 2
                    break
                if next_char == '"':
                    ret += '"'
                    i += 1
                elif next_char:
                    raise ParseError(string)
            else:
                ret += char
            i += 1
        return {"part": ret, "quoted": True, "next": string[i:]}

    def _normalize_id(self, part: dict | None, *, collapse_safe_quotes: bool = False) -> str | None:
        if not part:
            return None
        token = part["part"]
        quoted = bool(part["quoted"])
        if self.SF_ID_REGEX.match(token):
            if collapse_safe_quotes or not quoted:
                return token.upper() if self.force_upper_case else token
        return '"' + token.replace('"', '""') + '"'

    def canonical_identity(self, *, collapse_safe_quotes: bool = False) -> str:
        parts = [
            self._normalize_id(part, collapse_safe_quotes=collapse_safe_quotes)
            for part in [self.db_part, self.schema_part, self.entity_part]
            if part
        ]
        return ".".join(part for part in parts if part is not None)

    def to_list(self, *, collapse_safe_quotes: bool = False) -> list[str]:
        result = []
        for part in [self.db_part, self.schema_part, self.entity_part]:
            if part is not None:
                normalized = self._normalize_id(part, collapse_safe_quotes=collapse_safe_quotes)
                if normalized is not None:
                    result.append(normalized)
        identity = self.canonical_identity(collapse_safe_quotes=collapse_safe_quotes)
        if identity:
            result.append(identity)
        return result

    @staticmethod
    def to_sql_value(value: str) -> str:
        token = str(value)
        if token.startswith('"') and token.endswith('"'):
            token = token[1:-1]
        return "'" + token.replace("'", "''") + "'"


def split_qualified_name(name: str) -> list[str]:
    parser = IdentityParser(name, force_upper_case=False)
    return [part for part in [parser.db, parser.schema, parser.entity] if part is not None]


def unquote_identifier(name: str) -> str:
    token = str(name or "").strip()
    if len(token) >= 2 and token.startswith('"') and token.endswith('"'):
        return token[1:-1].replace('""', '"')
    return token


def quote_identifier_if_needed(name: str) -> str:
    parser = IdentityParser(name, force_upper_case=False)
    return parser.canonical_identity(collapse_safe_quotes=False)


def normalize_qualified_name(name: str) -> str:
    text = str(name or "").strip()
    if "." not in text:
        return quote_identifier_if_needed(unquote_identifier(text))
    return IdentityParser(text, force_upper_case=False).canonical_identity(collapse_safe_quotes=True)
