using System.Data;
using Insight.Database;
using PlanTempus.Core.Database.ConnectionFactory;
using PlanTempus.Database.Common;

namespace PlanTempus.Database.Core.DCL
{
	/// <summary>
	/// Only a superadmin or similar can create Application Users
	/// </summary>
	public class SetupApplicationUser : IDbConfigure<SetupApplicationUser.Command>
    {
        public class Command
        {
            public required string Schema { get; init; }
            public required string User { get; init; }
            public required string Password { get; init; }
        }

        Command _command;
        private readonly IDbConnectionFactory _connectionFactory;

        public SetupApplicationUser(IDbConnectionFactory connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }

        public void With(Command command, ConnectionStringParameters parameters = null)
        {
            _command = command;

            if (!Validations.IsValidSchemaName(_command.Schema))
                throw new ArgumentException("Invalid schema name", _command.Schema);

            using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters);
            using var transaction = conn.OpenWithTransaction();
            try
            {
                CreateSchema(conn);
                CreateRole(conn);
                GrantSchemaRights(conn);

                transaction.Commit();
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                throw new InvalidOperationException("Failed to SetupApplicationUser in Database", ex);
            }
        }

        private void CreateSchema(IDbConnection db)
        {
            var sql = $"CREATE SCHEMA IF NOT EXISTS {_command.Schema}";
            db.ExecuteSql(sql);
        }

        private void CreateRole(IDbConnection db)
        {
            var sql = $@"
							DO $$ 
							BEGIN
							   IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '{_command.User}') THEN
								   CREATE ROLE {_command.User} WITH CREATEDB CREATEROLE LOGIN PASSWORD '{_command.Password}';
							   END IF;
							END $$;";
            db.ExecuteSql(sql);

            var sql1 = $"ALTER ROLE {_command.User} SET search_path='{_command.Schema}';";
            db.ExecuteSql(sql1);
        }

        private void GrantSchemaRights(IDbConnection db)
        {
            // Grant USAGE og alle CREATE rettigheder på schema niveau
            var sql = $@"
						GRANT USAGE ON SCHEMA {_command.Schema} TO {_command.User};
						GRANT ALL ON SCHEMA {_command.Schema} TO {_command.User};";
            db.ExecuteSql(sql);

            // Grant rettigheder på eksisterende og fremtidige tabeller
            var sql1 = $"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA {_command.Schema} TO {_command.User};";
            db.ExecuteSql(sql1);

            var sql2 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT ALL PRIVILEGES ON TABLES TO {_command.User};";
            db.ExecuteSql(sql2);

            // Grant sequence rettigheder
            var sql3 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {_command.Schema} TO {_command.User};";
            db.ExecuteSql(sql3);

            // Grant execute på functions
            var sql4 = $"GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA {_command.Schema} TO {_command.User};";
            db.ExecuteSql(sql4);

            // Grant for fremtidige functions
            var sql5 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT EXECUTE ON FUNCTIONS TO {_command.User};";
            db.ExecuteSql(sql5);

            // Grant for fremtidige sequences
            var sql6 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT USAGE ON SEQUENCES TO {_command.User};";
            db.ExecuteSql(sql6);
        }

    }
}
