#if DEBUG
//#define liveShop
#endif
using Dapper;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;

namespace WK5.Core
{
    /// <summary>
    /// Stellt Funktionalitäten zur Arbeit mit einer MySQL-Datenbank zur Verfügung. Diese Klasse kann nicht vererbt werden.
    /// </summary>    
    public sealed class MySqlController2 : IDisposable
    {
        private bool _disposedValue;

        private MySqlConnection Connection => Command.Connection;
        private MySqlCommand Command { get; set; }

        /// <summary>
        /// Ruft den ConnectionString zur aktuellen Datenbank ab.
        /// </summary>
        public string ConnectionString { get; }
        #region Konstruktoren
        /// <summary>
        /// Erstellt einen neuen FbController und legt die Verbindung durch <see cref="GlobalConfig.ConnectionString"/> fest.
        /// </summary>
        public MySqlController2()
        {
#if DEBUG && !liveShop
            ConnectionString = GlobalConfig.NobelHobelMySQLConnectionString;
#else
            ConnectionString = GlobalConfig.KarleyMySQLConnectionString;
#endif

            Command = new MySqlCommand
            {
                Connection = new MySqlConnection(ConnectionString)
            };

            Command.Connection.Open();
        }

        public MySqlController2(string connectionString)
        {
            ConnectionString = connectionString;
            Command = new MySqlCommand
            {
                Connection = new MySqlConnection(ConnectionString)
            };

            Command.Connection.Open();
        }
        #endregion
        #region Hilfsmethoden
        public void ClearParameters() => Command?.Parameters?.Clear();
        public void AddParameter(string parameterName, object? value) => Command?.Parameters?.AddWithValue(parameterName, value);
        #endregion
        #region SQL-Methoden 
        /// <summary>
        /// Liefert alle Datensätze für eine beliebige SQL-Abfrage zurück. Diese Methode eignet sich für SELECT-Statements, wenn man mehr als ein Ergebnis erwartet. 
        /// <para>
        /// Benötigt man nur einen Datensatz, dann eignet sich besser die Methode
        /// <seealso cref="SelectRow(string)"/>
        /// </para>
        /// </summary>
        /// <param name="selectCommand">Beliebiges SQL. Es kann Auch ein SQL-String mit Platzhaltern für Parameter übergeben werden.</param>
        /// <returns>Alle Datensätze in einer DataTable. Wenn keine Datensätze gefunden wurden, dann gibt die Methode eine leere DataTable zurück.</returns>
        public async Task<DataTable> SelectDataAsync(string selectCommand)
        {
            DataTable data = new DataTable();
            Command.CommandType = CommandType.Text;
            Command.CommandText = selectCommand;

            using (MySqlDataAdapter adapter = new MySqlDataAdapter(Command))
            {
                await adapter.FillAsync(data);
            }
            ClearParameters();
            return data;
        }
        public async Task<List<T>> SelectDataAsync<T>(string selectCommand, object? param = null)
        {
            IEnumerable<T> enumerable = await Command.Connection.QueryAsync<T>(selectCommand, param);
            return enumerable.ToList();
        }
        /// <summary>
        /// Liefert den ersten gefunden Datensatz für eine SQL-Abfrage zurück. Diese Methode eignet sich für SELECT-Statements, wenn man nur ein Ergebnis erwartet.
        /// <para>
        /// Benötigt man mehr als einen Datensatz, dann eignet sich besser die Methode
        /// <seealso cref="SelectDataAsync(string)"/>
        /// </para>
        /// </summary>
        /// <param name="selectCommand">Beliebiges SQL. Es kann Auch ein SQL-String mit Platzhaltern für Parameter übergeben werden.</param>
        /// <returns>null wenn kein Ergebnis gefunden wurde, ansonsten die erste gefundene DataRow</returns>
        public async Task<DataRow?> SelectRowAsync(string selectCommand)
        {

            DataTable data = new DataTable();
            Command.CommandType = CommandType.Text;
            Command.CommandText = selectCommand;

            using (MySqlDataAdapter adapter = new MySqlDataAdapter(Command))
            {
                await adapter.FillAsync(data);
            }

            ClearParameters();
            return data.Rows.Count > 0 ? data.Rows[0] : null;
        }
        /// <summary>
        /// Führt einen beliebigen SQL-Command am Server aus. Diese Methode eignet sich für UPDATE, DELETE und INSERT-Statements.
        /// <para>
        /// Die Methode verwendet für den Befehl <see cref="CommandType.Text"/>
        /// </para>
        /// </summary>
        /// <param name="command">Beliebiges SQL. Es kann Auch ein SQL-String mit Platzhaltern für Parameter übergeben werden.</param>
        public async Task QueryAsync(string sqlCommand)
        {
            Command.CommandText = sqlCommand;
            Command.CommandType = CommandType.Text;
            await Command.ExecuteNonQueryAsync();
            ClearParameters();
        }
        /// <summary>
        /// Führt eine Prozedur in der Datenbank aus
        /// </summary>
        /// <param name="procedureName">Der Name der Prozedur. Dieser kann aus der Datenbank entnommen werden.</param>
        public async Task RunProcedureAsync(string procedureName)
        {
            Command.CommandType = CommandType.StoredProcedure;
            Command.CommandText = procedureName;
            await Command.ExecuteNonQueryAsync();
            ClearParameters();
        }

        public async Task<object?> FetchObjectAsync(string selectCommand)
        {
            Command.CommandText = selectCommand;
            Command.CommandType = CommandType.Text;
            object? returnVal = await Command.ExecuteScalarAsync();

            ClearParameters();
            return returnVal;
        }
        #endregion
        #region Transaction
        /// <summary>
        /// Startet eine neue Transaction.
        /// </summary>
        /// <exception cref="InvalidOperationException">Tritt auf, wenn bereits eine Transaction läuft.</exception>        
        public async Task StartTransactionAsync()
        {
            if (Command.Transaction != null)
            {
                throw new InvalidOperationException($"Es konnte keine Transaction gestartet werden, da bereits eine Transaction läuft");
            }

            Command.Transaction = await Command.Connection.BeginTransactionAsync();
        }
        /// <summary>
        /// Committed alle Änderungen aus der Pipe-Line
        /// <para>
        /// Beim Aufruf der Methode wird die Transaction abgeschlossen und kann nicht mehr verwendet werden.
        /// </para>
        /// </summary>
        public async Task CommitChangesAsync()
        {
            try
            {
                await Command.Transaction.CommitAsync();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                Command.Transaction?.Dispose();
                Command.Transaction = null;
            }
        }
        /// <summary>
        /// Hebt alle ausstehenden Änderungen der Transaction auf.
        /// <para>
        /// Beim Aufruf der Methode wird die Transaction abgeschlossen und kann nicht mehr verwendet werden.
        /// </para>
        /// </summary>
        public async Task RollbackChangesAsync()
        {
            try
            {
                await Command.Transaction.RollbackAsync();
            }
            catch (Exception)
            {

                throw;
            }
        }
        #endregion
        #region IDisposable
        private void Dispose(bool disposing)
        {
            if (!_disposedValue)
            {
                if (disposing)
                {
                    Connection.Dispose();
                    Command.Dispose();
                }

                _disposedValue = true;
            }
        }



        public void Dispose()
        {
            // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "Dispose(bool disposing)" ein.
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
