// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::ast::helpers::attached_token::AttachedToken;
use crate::ast::{
    BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, CreateTrigger,
    GranteesType, IfStatement, Statement,
};
use crate::dialect::Dialect;
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
use crate::tokenizer::Token;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};

/// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/)
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MsSqlDialect {}

impl Dialect for MsSqlDialect {
    fn is_delimited_identifier_start(&self, ch: char) -> bool {
        ch == '"' || ch == '['
    }

    fn is_identifier_start(&self, ch: char) -> bool {
        // See https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-2017#rules-for-regular-identifiers
        ch.is_alphabetic() || ch == '_' || ch == '#' || ch == '@'
    }

    fn is_identifier_part(&self, ch: char) -> bool {
        ch.is_alphabetic()
            || ch.is_ascii_digit()
            || ch == '@'
            || ch == '$'
            || ch == '#'
            || ch == '_'
    }

    fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
        Some('[')
    }

    /// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
    /// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
    fn convert_type_before_value(&self) -> bool {
        true
    }

    fn supports_outer_join_operator(&self) -> bool {
        true
    }

    /// SQL Server supports `$` as a prefix for money literals
    /// <https://learn.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql?view=sql-server-ver17#money-constants>
    fn supports_dollar_as_money_prefix(&self) -> bool {
        true
    }

    fn supports_connect_by(&self) -> bool {
        true
    }

    fn supports_eq_alias_assignment(&self) -> bool {
        true
    }

    fn supports_try_convert(&self) -> bool {
        true
    }

    /// In MSSQL, there is no boolean type, and `true` and `false` are valid column names
    fn supports_boolean_literals(&self) -> bool {
        false
    }

    fn supports_named_fn_args_with_colon_operator(&self) -> bool {
        true
    }

    fn supports_named_fn_args_with_expr_name(&self) -> bool {
        true
    }

    fn supports_named_fn_args_with_rarrow_operator(&self) -> bool {
        false
    }

    fn supports_start_transaction_modifier(&self) -> bool {
        true
    }

    fn supports_end_transaction_modifier(&self) -> bool {
        true
    }

    /// See: <https://learn.microsoft.com/en-us/sql/t-sql/statements/set-statements-transact-sql>
    fn supports_set_stmt_without_operator(&self) -> bool {
        true
    }

    /// See: <https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table>
    fn supports_table_versioning(&self) -> bool {
        true
    }

    /// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/slash-star-comment-transact-sql?view=sql-server-ver16>
    fn supports_nested_comments(&self) -> bool {
        true
    }

    /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql>
    fn supports_object_name_double_dot_notation(&self) -> bool {
        true
    }

    /// See <https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles>
    fn get_reserved_grantees_types(&self) -> &[GranteesType] {
        &[GranteesType::Public]
    }

    fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
        match kw {
            // List of keywords that cannot be used as select item (column) aliases in MSSQL
            // regardless of whether the alias is explicit or implicit.
            //
            // These are T-SQL statement-starting keywords; allowing them as implicit aliases
            // causes the parser to consume the keyword as an alias for the previous expression,
            // then fail on the token that follows (e.g. `TABLE`, `@var`, `sp_name`, …).
            Keyword::IF
            | Keyword::ELSE
            | Keyword::DECLARE
            | Keyword::EXEC
            | Keyword::EXECUTE
            | Keyword::INSERT
            | Keyword::UPDATE
            | Keyword::DELETE
            | Keyword::DROP
            | Keyword::CREATE
            | Keyword::ALTER
            | Keyword::TRUNCATE
            | Keyword::PRINT
            | Keyword::WHILE
            | Keyword::RETURN
            | Keyword::THROW
            | Keyword::RAISERROR
            | Keyword::MERGE => false,
            _ => explicit || self.is_column_alias(kw, parser),
        }
    }

    fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
        match kw {
            // List of keywords that cannot be used as table aliases in MSSQL
            // regardless of whether the alias is explicit or implicit.
            //
            // These are T-SQL statement-starting keywords. Without blocking them here,
            // a bare `SELECT * FROM t` followed by a newline and one of these keywords
            // would cause the parser to consume the keyword as a table alias for `t`,
            // then fail on the token that follows (e.g. `@var`, `sp_name`, `TABLE`, …).
            //
            // `SET` is already covered by the global `RESERVED_FOR_TABLE_ALIAS` list;
            // the keywords below are MSSQL-specific additions.
            Keyword::IF
            | Keyword::ELSE
            | Keyword::DECLARE
            | Keyword::EXEC
            | Keyword::EXECUTE
            | Keyword::INSERT
            | Keyword::UPDATE
            | Keyword::DELETE
            | Keyword::DROP
            | Keyword::CREATE
            | Keyword::ALTER
            | Keyword::TRUNCATE
            | Keyword::PRINT
            | Keyword::WHILE
            | Keyword::RETURN
            | Keyword::THROW
            | Keyword::RAISERROR
            | Keyword::MERGE => false,
            _ => explicit || self.is_table_alias(kw, parser),
        }
    }

    fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
        if parser.parse_keyword(Keyword::BEGIN) {
            // Check if this is a BEGIN...END block rather than BEGIN TRANSACTION
            let is_block = parser
                .maybe_parse(|p| {
                    if p.parse_transaction_modifier().is_some()
                        || p.parse_one_of_keywords(&[
                            Keyword::TRANSACTION,
                            Keyword::WORK,
                            Keyword::TRAN,
                        ])
                        .is_some()
                        || matches!(p.peek_token_ref().token, Token::SemiColon | Token::EOF)
                    {
                        p.expected_ref("statement", p.peek_token_ref())
                    } else {
                        Ok(())
                    }
                })
                .unwrap_or(None)
                .is_some();
            if is_block {
                Some(parser.parse_begin_exception_end())
            } else {
                parser.prev_token();
                None
            }
        } else if parser.peek_keyword(Keyword::IF) {
            Some(self.parse_if_stmt(parser))
        } else if parser.parse_keywords(&[Keyword::CREATE, Keyword::TRIGGER]) {
            Some(self.parse_create_trigger(parser, false))
        } else if parser.parse_keywords(&[
            Keyword::CREATE,
            Keyword::OR,
            Keyword::ALTER,
            Keyword::TRIGGER,
        ]) {
            Some(self.parse_create_trigger(parser, true))
        } else {
            None
        }
    }

    fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> {
        let token = parser.peek_token_ref();
        match &token.token {
            // lowest prec to prevent it from turning into a binary op
            Token::Colon => Some(Ok(self.prec_unknown())),
            _ => None,
        }
    }
}

impl MsSqlDialect {
    /// ```sql
    /// IF boolean_expression
    ///     { sql_statement | statement_block }
    /// [ ELSE
    ///     { sql_statement | statement_block } ]
    /// ```
    fn parse_if_stmt(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
        let if_token = parser.expect_keyword(Keyword::IF)?;

        let condition = parser.parse_expr()?;

        let if_block = if parser.peek_keyword(Keyword::BEGIN) {
            let begin_token = parser.expect_keyword(Keyword::BEGIN)?;
            let statements = self.parse_statement_list(parser, Some(Keyword::END))?;
            let end_token = parser.expect_keyword(Keyword::END)?;
            ConditionalStatementBlock {
                start_token: AttachedToken(if_token),
                condition: Some(condition),
                then_token: None,
                conditional_statements: ConditionalStatements::BeginEnd(BeginEndStatements {
                    begin_token: AttachedToken(begin_token),
                    statements,
                    end_token: AttachedToken(end_token),
                }),
            }
        } else {
            let stmt = parser.parse_statement()?;
            ConditionalStatementBlock {
                start_token: AttachedToken(if_token),
                condition: Some(condition),
                then_token: None,
                conditional_statements: ConditionalStatements::Sequence {
                    statements: vec![stmt],
                },
            }
        };

        let mut prior_statement_ended_with_semi_colon = false;
        while let Token::SemiColon = parser.peek_token_ref().token {
            parser.advance_token();
            prior_statement_ended_with_semi_colon = true;
        }

        let mut else_block = None;
        if parser.peek_keyword(Keyword::ELSE) {
            let else_token = parser.expect_keyword(Keyword::ELSE)?;
            if parser.peek_keyword(Keyword::BEGIN) {
                let begin_token = parser.expect_keyword(Keyword::BEGIN)?;
                let statements = self.parse_statement_list(parser, Some(Keyword::END))?;
                let end_token = parser.expect_keyword(Keyword::END)?;
                else_block = Some(ConditionalStatementBlock {
                    start_token: AttachedToken(else_token),
                    condition: None,
                    then_token: None,
                    conditional_statements: ConditionalStatements::BeginEnd(BeginEndStatements {
                        begin_token: AttachedToken(begin_token),
                        statements,
                        end_token: AttachedToken(end_token),
                    }),
                });
            } else {
                let stmt = parser.parse_statement()?;
                else_block = Some(ConditionalStatementBlock {
                    start_token: AttachedToken(else_token),
                    condition: None,
                    then_token: None,
                    conditional_statements: ConditionalStatements::Sequence {
                        statements: vec![stmt],
                    },
                });
            }
        } else if prior_statement_ended_with_semi_colon {
            parser.prev_token();
        }

        Ok(IfStatement {
            if_block,
            else_block,
            elseif_blocks: Vec::new(),
            end_token: None,
        }
        .into())
    }

    /// Parse `CREATE TRIGGER` for [MsSql]
    ///
    /// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
    fn parse_create_trigger(
        &self,
        parser: &mut Parser,
        or_alter: bool,
    ) -> Result<Statement, ParserError> {
        let name = parser.parse_object_name(false)?;
        parser.expect_keyword_is(Keyword::ON)?;
        let table_name = parser.parse_object_name(false)?;
        let period = parser.parse_trigger_period()?;
        let events = parser.parse_comma_separated(Parser::parse_trigger_event)?;

        parser.expect_keyword_is(Keyword::AS)?;
        let statements = Some(parser.parse_conditional_statements(&[Keyword::END])?);

        Ok(CreateTrigger {
            or_alter,
            temporary: false,
            or_replace: false,
            is_constraint: false,
            name,
            period: Some(period),
            period_before_table: false,
            events,
            table_name,
            referenced_table_name: None,
            referencing: Vec::new(),
            trigger_object: None,
            condition: None,
            exec_body: None,
            statements_as: true,
            statements,
            characteristics: None,
        }
        .into())
    }

    /// Parse a sequence of statements, optionally separated by semicolon.
    ///
    /// Stops parsing when reaching EOF or the given keyword.
    fn parse_statement_list(
        &self,
        parser: &mut Parser,
        terminal_keyword: Option<Keyword>,
    ) -> Result<Vec<Statement>, ParserError> {
        let mut stmts = Vec::new();
        loop {
            if let Token::EOF = parser.peek_token_ref().token {
                break;
            }
            if let Some(term) = terminal_keyword {
                if parser.peek_keyword(term) {
                    break;
                }
            }
            stmts.push(parser.parse_statement()?);
            while let Token::SemiColon = parser.peek_token_ref().token {
                parser.advance_token();
            }
        }
        Ok(stmts)
    }
}
