// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include <winget/SQLiteWrapper.h>
#include <AppInstallerLanguageUtilities.h>

#include <functional>
#include <initializer_list>
#include <memory>
#include <optional>
#include <sstream>
#include <string_view>
#include <vector>

using namespace std::string_view_literals;

namespace AppInstaller::SQLite::Builder
{
    namespace details
    {
        // Sentinel types to indicate special cases to the builder.
        struct unbound_t {};
        struct rowcount_t {};

        // Class for intake from external functions.
        struct SubBuilder
        {
            SubBuilder(std::string&& s) : m_string(std::move(s)) {}

            SubBuilder(const SubBuilder&) = default;
            SubBuilder& operator=(const SubBuilder&) = default;

            SubBuilder(SubBuilder&&) noexcept = default;
            SubBuilder& operator=(SubBuilder&&) noexcept = default;

            const std::string& GetString() const { return m_string; }

        protected:
            std::string m_string;
        };

        // Base class for all sub-builders.
        struct SubBuilderBase
        {
            SubBuilderBase() = default;

            SubBuilderBase(const SubBuilderBase&) = default;
            SubBuilderBase& operator=(const SubBuilderBase&) = default;

            SubBuilderBase(SubBuilderBase&&) noexcept = default;
            SubBuilderBase& operator=(SubBuilderBase&&) noexcept = default;

            virtual operator SubBuilder() { return { m_stream.str() }; }

        protected:
            std::ostringstream m_stream;
        };
    }

    // Pass this value to indicate that the caller will bind the value later.
    __declspec_selectany_ details::unbound_t Unbound;

    // Pass this value to indicate that the number of rows is to be selected.
    __declspec_selectany_ details::rowcount_t RowCount;

    // A qualified table reference.
    struct QualifiedTable
    {
        std::string_view Schema;
        std::string_view Table;

        explicit constexpr QualifiedTable(std::string_view table) : Table(table) {}
        explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {}
    };

    namespace Schema
    {
        // The main database's schema table.
        // More info can be found at: https://www.sqlite.org/schematab.html
        constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv };

        // The sqlite_schema column name for the type of the object.
        constexpr std::string_view TypeColumn = "type"sv;

        // The sqlite_schema type value for a table.
        constexpr std::string_view Type_Table = "table"sv;

        // The sqlite_schema type value for an index.
        constexpr std::string_view Type_Index = "index"sv;

        // The sqlite_schema column name for the name of the object.
        constexpr std::string_view NameColumn = "name"sv;
    }

    // A qualified column reference.
    struct QualifiedColumn
    {
        std::string_view Table;
        std::string_view Column;

        explicit QualifiedColumn(std::string_view column) : Column(column) {}
        explicit QualifiedColumn(std::string_view table, std::string_view column) : Table(table), Column(column) {}
    };

    // SQLite types as an enum.
    enum class Type
    {
        Int,
        Bool = Int,
        Int64,
        RowId = Int64,
        Text,
        Blob,
        Integer, // Type for specifying a primary key column as a row id alias.
        None, // Does not declare a type
    };

    template <Type type, bool allowNull>
    struct TypeInfo
    {
    };

    template <>
    struct TypeInfo<Type::Text, false>
    {
        using value_t = std::string;
    };

    template <>
    struct TypeInfo<Type::Text, true>
    {
        using value_t = std::optional<std::string>;
    };

    template <>
    struct TypeInfo<Type::Blob, false>
    {
        using value_t = SQLite::blob_t;
    };

    template <>
    struct TypeInfo<Type::Blob, true>
    {
        using value_t = std::optional<SQLite::blob_t>;
    };

    // Aggregate functions.
    enum class Aggregate
    {
        Min,
        Max,
    };

    // Helper to mark create an integer primary key for rowid, making it stable across vacuum.
    struct IntegerPrimaryKey : public details::SubBuilderBase
    {
        IntegerPrimaryKey();

        IntegerPrimaryKey(const IntegerPrimaryKey&) = default;
        IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default;

        IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default;
        IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default;

        // Set the column to autoincrement. SQLite recommends against using this value unless
        // you need to ensure that rowids are not ever reused.
        IntegerPrimaryKey& AutoIncrement(bool isTrue = true);
    };

    // Helper used when creating a table.
    struct ColumnBuilder : public details::SubBuilderBase
    {
        // Specify the column name and type when creating the builder.
        ColumnBuilder(std::string_view column, Type type);

        ColumnBuilder(const ColumnBuilder&) = default;
        ColumnBuilder& operator=(const ColumnBuilder&) = default;

        ColumnBuilder(ColumnBuilder&&) noexcept = default;
        ColumnBuilder& operator=(ColumnBuilder&&) noexcept = default;

        // Indicate that the column is not able to be null.
        // Allow for data driven construction with input value.
        ColumnBuilder& NotNull(bool isTrue = true);

        // Indicate that the column is case-insensitive.
        // Allow for data driven construction with input value.
        ColumnBuilder& CollateNoCase(bool isTrue = true);

        // Indicate the default value for the column.
        // Note that a default value is not considered constant if it is bound,
        // so this function directly places the incoming value into the SQL statement.
        ColumnBuilder& Default(int64_t value);

        // Indicate that the column is unique.
        // Allow for data driven construction with input value.
        ColumnBuilder& Unique(bool isTrue = true);

        // Indicate that the column is the primary key.
        // Allow for data driven construction with input value.
        ColumnBuilder& PrimaryKey(bool isTrue = true);
    };

    // Helper used to specify a primary key with multiple columns.
    struct PrimaryKeyBuilder : public details::SubBuilderBase
    {
        PrimaryKeyBuilder();
        PrimaryKeyBuilder(std::initializer_list<std::string_view> columns);

        PrimaryKeyBuilder(const PrimaryKeyBuilder&) = default;
        PrimaryKeyBuilder& operator=(const PrimaryKeyBuilder&) = default;

        PrimaryKeyBuilder(PrimaryKeyBuilder&&) noexcept = default;
        PrimaryKeyBuilder& operator=(PrimaryKeyBuilder&&) noexcept = default;

        virtual operator details::SubBuilder() override;

        // Add a column to the primary key.
        PrimaryKeyBuilder& Column(std::string_view column);

    private:
        bool m_isFirst = true;
        bool m_needsClosing = true;
    };

    // A class that aids in building SQL statements in a more expressive manner than simple strings.
    struct StatementBuilder
    {
        StatementBuilder() = default;

        StatementBuilder(const StatementBuilder&) = default;
        StatementBuilder& operator=(const StatementBuilder&) = default;

        StatementBuilder(StatementBuilder&&) = default;
        StatementBuilder& operator=(StatementBuilder&&) = default;

        // Begin a select statement for the given columns.
        StatementBuilder& Select();
        StatementBuilder& Select(std::string_view column);
        StatementBuilder& Select(std::initializer_list<std::string_view> columns);
        StatementBuilder& Select(const QualifiedColumn& column);
        StatementBuilder& Select(std::initializer_list<QualifiedColumn> columns);
        StatementBuilder& Select(details::rowcount_t);

        // Indicate the table that the statement will be operating on.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& From();
        StatementBuilder& From(std::string_view table);
        StatementBuilder& From(QualifiedTable table);
        StatementBuilder& From(std::initializer_list<std::string_view> table);

        // Begin a filter clause on the given column.
        StatementBuilder& Where(std::string_view column);
        StatementBuilder& Where(const QualifiedColumn& column);

        // A full filter clause looking for an embedded null character.
        // Is extremely specific to consistency checks, and so a more detailed construct is not required.
        StatementBuilder& WhereValueContainsEmbeddedNullCharacter(std::string_view column);
        StatementBuilder& WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column);

        // Indicate the operation of the filter clause.
        template <typename ValueType>
        StatementBuilder& Equals(const ValueType& value)
        {
            AddBindFunctor(AppendOpAndBinder(Op::Equals), value);
            return *this;
        }
        template <typename ValueType>
        StatementBuilder& Equals(const std::optional<ValueType>& value)
        {
            if (value)
            {
                AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value());
                return *this;
            }
            else
            {
                return IsNull();
            }
        }
        // The optional index value can be used to specify the parameter index.
        StatementBuilder& Equals(details::unbound_t, std::optional<size_t> index = {});
        StatementBuilder& Equals(std::nullptr_t);
        StatementBuilder& Equals();
        StatementBuilder& Equals(const QualifiedColumn& column);

        template <typename ValueType>
        StatementBuilder& IsGreaterThan(const ValueType& value)
        {
            AddBindFunctor(AppendOpAndBinder(Op::GreaterThan), value);
            return *this;
        }
        StatementBuilder& IsGreaterThan(details::unbound_t, std::optional<size_t> index = {});

        template <typename ValueType>
        StatementBuilder& IsGreaterThanOrEqualTo(const ValueType& value)
        {
            AddBindFunctor(AppendOpAndBinder(Op::GreaterThanOrEqualTo), value);
            return *this;
        }
        StatementBuilder& IsGreaterThanOrEqualTo(details::unbound_t, std::optional<size_t> index = {});

        StatementBuilder& LikeWithEscape(std::string_view value);
        StatementBuilder& Like(details::unbound_t);

        StatementBuilder& Escape(std::string_view escapeChar);

        StatementBuilder& Not();
        StatementBuilder& In();

        // Appends a set of value binders for the In clause.
        StatementBuilder& In(size_t count);

        // IsNull(true) means the value is null; IsNull(false) means the value is not null.
        StatementBuilder& IsNull(bool isNull = true);
        StatementBuilder& IsNotNull() { return IsNull(false); }

        // Operators for combining filter clauses.
        StatementBuilder& And(std::string_view column);
        StatementBuilder& And(const QualifiedColumn& column);
        StatementBuilder& Or(const QualifiedColumn& column);

        // Begin a join clause.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& Join(std::string_view table);
        StatementBuilder& Join(QualifiedTable table);
        StatementBuilder& Join(std::initializer_list<std::string_view> table);

        // Begin a left outer join clause.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& LeftOuterJoin(std::string_view table);
        StatementBuilder& LeftOuterJoin(QualifiedTable table);
        StatementBuilder& LeftOuterJoin(std::initializer_list<std::string_view> table);

        // Set the join constraint.
        StatementBuilder& On(const QualifiedColumn& column1, const QualifiedColumn& column2);

        // Specify the grouping to use.
        StatementBuilder& GroupBy(std::string_view column);
        StatementBuilder& GroupBy(const QualifiedColumn& column);

        // Specify the ordering to use.
        StatementBuilder& OrderBy(std::string_view column);
        StatementBuilder& OrderBy(const QualifiedColumn& column);
        StatementBuilder& OrderBy(std::initializer_list<std::string_view> columns);

        // Specify the ordering behavior.
        StatementBuilder& Ascending();
        StatementBuilder& Descending();

        // Limits the result set to the given number of rows.
        StatementBuilder& Limit(size_t rowCount);

        // Begin an insert statement for the given table.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& InsertInto(std::string_view table);
        StatementBuilder& InsertInto(QualifiedTable table);
        StatementBuilder& InsertInto(std::initializer_list<std::string_view> table);

        // Begin an insert or ignore statement for the given table.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& InsertOrIgnore(std::string_view table);
        StatementBuilder& InsertOrIgnore(QualifiedTable table);
        StatementBuilder& InsertOrIgnore(std::initializer_list<std::string_view> table);

        // Set the columns for a statement (typically insert).
        StatementBuilder& Columns(std::string_view column);
        StatementBuilder& Columns(std::initializer_list<std::string_view> columns);
        StatementBuilder& Columns(const QualifiedColumn& column);
        StatementBuilder& Columns(std::initializer_list<QualifiedColumn> columns);

        // Set the columns for a select or create table statement.
        StatementBuilder& Columns(std::initializer_list<details::SubBuilder> columns);
        StatementBuilder& BeginColumns();
        StatementBuilder& Column(std::string_view column);
        StatementBuilder& Column(const QualifiedColumn& column);
        StatementBuilder& Column(Aggregate aggOp, std::string_view column);
        StatementBuilder& Column(Aggregate aggOp, const QualifiedColumn& column);
        StatementBuilder& Column(const details::SubBuilder& column);
        StatementBuilder& EndColumns();

        // Set the columns null constraint.
        StatementBuilder& NotNull(bool isTrue = true);

        // Set the column's default value.
        template <typename ValueType>
        StatementBuilder& Default(const ValueType& value)
        {
            m_stream << " DEFAULT (" << value << ")";
            return *this;
        }

        // Add the values clause for an insert statement.
        template <typename... ValueTypes>
        StatementBuilder& Values(const ValueTypes&... values)
        {
            int bindIndexBegin = AppendValuesAndBinders(sizeof...(ValueTypes));
            // Use folding to add a binder for every value, specifically in the order they were given.
            // Do not change this expression without understanding the implications to the bind order.
            // See: https://en.cppreference.com/w/cpp/language/fold for more details.
            (FoldHelper{}, ..., InsertValuesValueBinder(bindIndexBegin++, values));
            return *this;
        }
        StatementBuilder& BeginValues();
        template <typename ValueType>
        StatementBuilder& Value(const ValueType& value)
        {
            InsertValuesValueBinder(AppendValueAndBinder(), value);
            return *this;
        }
        StatementBuilder& EndValues();

        // Begin a table creation statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& CreateTable(std::string_view table);
        StatementBuilder& CreateTable(QualifiedTable table);
        StatementBuilder& CreateTable(std::initializer_list<std::string_view> table);

        // Begin an alter table statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& AlterTable(std::string_view table);
        StatementBuilder& AlterTable(QualifiedTable table);
        StatementBuilder& AlterTable(std::initializer_list<std::string_view> table);

        // Complete an alter table statement by adding a column.
        StatementBuilder& Add(std::string_view column, Type type);

        // Begin a table deletion statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& DropTable(std::string_view table);
        StatementBuilder& DropTable(QualifiedTable table);
        StatementBuilder& DropTable(std::initializer_list<std::string_view> table);

        // Begin a table deletion statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& DropTableIfExists(std::string_view table);
        StatementBuilder& DropTableIfExists(QualifiedTable table);
        StatementBuilder& DropTableIfExists(std::initializer_list<std::string_view> table);

        // Begin an index creation statement.
        // The initializer_list form enables the index name to be constructed from multiple parts.
        StatementBuilder& CreateIndex(std::string_view table);
        StatementBuilder& CreateIndex(QualifiedTable table);
        StatementBuilder& CreateIndex(std::initializer_list<std::string_view> table);

        // Begin an unique index creation statement.
        // The initializer_list form enables the index name to be constructed from multiple parts.
        StatementBuilder& CreateUniqueIndex(std::string_view table);
        StatementBuilder& CreateUniqueIndex(QualifiedTable table);
        StatementBuilder& CreateUniqueIndex(std::initializer_list<std::string_view> table);

        // Begin an index deletion statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& DropIndex(std::string_view index);
        StatementBuilder& DropIndex(QualifiedTable index);
        StatementBuilder& DropIndex(std::initializer_list<std::string_view> index);

        // Set index target table.
        StatementBuilder& On(std::string_view table);
        StatementBuilder& On(std::initializer_list<std::string_view> table);

        // Begin a delete statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& DeleteFrom(std::string_view table);
        StatementBuilder& DeleteFrom(QualifiedTable table);
        StatementBuilder& DeleteFrom(std::initializer_list<std::string_view> table);

        // Begin an update statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& Update(std::string_view table);
        StatementBuilder& Update(QualifiedTable table);
        StatementBuilder& Update(std::initializer_list<std::string_view> table);

        // Begin an `update or replace` statement.
        // The initializer_list form enables the table name to be constructed from multiple parts.
        StatementBuilder& UpdateOrReplace(std::string_view table);
        StatementBuilder& UpdateOrReplace(QualifiedTable table);
        StatementBuilder& UpdateOrReplace(std::initializer_list<std::string_view> table);

        // Output the set portion of an update statement.
        StatementBuilder& Set();

        // Output the set portion of an update statement.
        StatementBuilder& Vacuum();

        // General purpose functions to begin and end a parenthetical expression.
        StatementBuilder& BeginParenthetical();
        StatementBuilder& EndParenthetical();

        // Adds the `without rowid` clause.
        StatementBuilder& WithoutRowID();

        // Assign an alias to the previous item.
        StatementBuilder& As(std::string_view alias);

        // Gets the last bound index.
        // A value of zero indicates that nothing has been bound.
        int GetLastBindIndex() const { return m_bindIndex - 1; }

        // Prepares and returns the statement, applying any bindings that were requested.
        Statement Prepare(const Connection& connection);

        // A convenience function that prepares, binds, and then executes a statement that does not return rows.
        void Execute(const Connection& connection);

    private:
        enum class Op
        {
            Equals,
            Like,
            Escape,
            Literal,
            GreaterThan,
            GreaterThanOrEqualTo,
        };

        // Appends given the operation.
        // The optional index value can be used to specify the parameter index.
        int AppendOpAndBinder(Op op, std::optional<size_t> index = {});

        // Appends a set of binders for the values clause of an insert.
        int AppendValuesAndBinders(size_t count);

        // Appends a binder for the values clause of an insert.
        int AppendValueAndBinder();

        // Adds a functor to our list that will bind the given value.
        template <typename ValueType>
        void AddBindFunctor(int binderIndex, const ValueType& value)
        {
            m_binders.emplace_back([binderIndex, value](Statement& s) { s.Bind(binderIndex, value); });
        }

        // Helper template for binding incoming values for an insert.
        template <typename ValueType>
        StatementBuilder& InsertValuesValueBinder(int bindIndex, const ValueType& value)
        {
            AddBindFunctor(bindIndex, value);
            return *this;
        }
        template <typename ValueType>
        StatementBuilder& InsertValuesValueBinder(int bindIndex, const std::optional<ValueType>& value)
        {
            if (value)
            {
                AddBindFunctor(bindIndex, value.value());
            }
            else
            {
                AddBindFunctor(bindIndex, nullptr);
            }
            return *this;
        }
        StatementBuilder& InsertValuesValueBinder(int, details::unbound_t)
        {
            return *this;
        }
        StatementBuilder& InsertValuesValueBinder(int bindIndex, std::nullptr_t)
        {
            AddBindFunctor(bindIndex, nullptr);
            return *this;
        }

        std::ostringstream m_stream;
        // Because binding values starts at 1
        int m_bindIndex = 1;
        std::vector<std::function<void(Statement&)>> m_binders;
        bool m_needsComma = false;
    };
}
