using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Microsoft.Data.Sqlite;
using System.Reflection;
using System.Windows.Forms;
using System.Configuration;
using System.Diagnostics;

namespace DataOrganizer
{
    public class DB
    {
        public static string DatabasePath { get; private set; }
        public static string ConnectionString { get; private set; }
        public static string TempScriptLocal = "Y:\\Database Template.sql";

        public DB()
        {
            DatabasePath = $"{new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath).Directory.FullName}\\Organizer Data\\Main.sqlite";

            ConnectionString = $"Data Source={DatabasePath}";

            if (!File.Exists(DatabasePath))
            {
                Directory.CreateDirectory(new FileInfo(DatabasePath).Directory.FullName);
                CreateDatabase();
            }
        }

        public void DeleteDatabase()
        {
            if (File.Exists(DatabasePath)) File.Delete(DatabasePath);
        }

        private void CreateDatabase()
        {
            //TODO: this is tmp
            var query = File.ReadAllText(TempScriptLocal);

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using(var command = new SqliteCommand(query, connection))
                {
                    connection.Open();

                    command.ExecuteNonQuery();
                }
            }
        }

        public List<Profile> GetProfiles()
        {
            var query = "SELECT * FROM profile";

            var profiles = new List<Profile>();

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    connection.Open();

                    var reader = command.ExecuteReader();

                    while (reader.Read())
                    {
                        profiles.Add(new Profile
                        {
                            Name = reader["profile_name"].ToString(),
                            DefaultNewContentFolder = reader["default_new_content_folder_name"].ToString(),
                            MachineName = reader["machine_name"].ToString(),
                            Id = Convert.ToInt32(reader["profile_id"]),
                            FolderGroups = new List<FolderGroup>()
                        });
                    }

                    reader.Close();

                    command.CommandText = "SELECT root_id, name, path, pattern_id FROM folder_root WHERE profile_id = @ID ORDER BY name";

                    foreach(var profile in profiles)
                    {
                        command.Parameters.Clear();
                        command.Parameters.AddWithValue("ID", profile.Id);

                        reader = command.ExecuteReader();

                        while(reader.Read())
                        {
                            var type = OrganizationType.Custom;
                            var patternId = Convert.ToInt32(reader["pattern_id"]);

                            switch (patternId)
                            {
                                case 1:
                                    type = OrganizationType.SingleLevel;
                                    break;
                                case 2:
                                    type = OrganizationType.Flat;
                                    break;
                                default:
                                    type = OrganizationType.Custom;
                                    break;
                            }

                            profile.FolderGroups.Add(new FolderGroup
                            {
                                Name = reader["name"].ToString(),
                                Id = Convert.ToInt32(reader["root_id"]),
                                Path = reader["path"].ToString(),
                                PatternId = Convert.ToInt32(reader["pattern_id"]),
                                Type = type
                            });
                        }

                        reader.Close();
                    }
                }
            }

            return profiles;
        }

        public bool UpdateProfile(Profile profile)
        {
            var query = @"UPDATE main.profile SET machine_name = @MachineName, default_new_content_folder_name = @DefaultPath, profile_name = @Name WHERE profile_id = @ID";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("MachineName", profile.MachineName);
                    command.Parameters.AddWithValue("DefaultPath", profile.DefaultNewContentFolder);
                    command.Parameters.AddWithValue("Name", profile.Name);
                    command.Parameters.AddWithValue("ID", profile.Id);

                    connection.Open();

                    command.ExecuteNonQuery();
                }
            }

            return true;
        }

        public int InsertNewProfile(Profile profile)
        {
            var query = @"INSERT INTO main.profile (machine_name, default_new_content_folder_name, profile_name) VALUES (@MachineName, @DefaultPath, @Name)";
            var id = -1;

            using (var connection = new SqliteConnection(ConnectionString))
            {
                connection.Open();

                using (var transaction = connection.BeginTransaction())
                {
                    using (var command = new SqliteCommand(query, connection, transaction))
                    {
                        command.Parameters.AddWithValue("MachineName", profile.MachineName);
                        command.Parameters.AddWithValue("DefaultPath", profile.DefaultNewContentFolder);
                        command.Parameters.AddWithValue("Name", profile.Name);

                        command.ExecuteNonQuery();

                        command.CommandText = "SELECT MAX(profile_id) FROM main.profile";

                        id = Convert.ToInt32(command.ExecuteScalar());

                        command.CommandText = "INSERT INTO main.folder_root (name, path, profile_id, pattern_id) VALUES (@Name, @Path, @ProfileID, @Pattern)";

                        if (profile.FolderGroups != null)
                        {
                            foreach(var group in profile.FolderGroups)
                            {
                                command.Parameters.Clear();
                                command.Parameters.AddWithValue("Name", group.Name);
                                command.Parameters.AddWithValue("Path", group.Path);
                                command.Parameters.AddWithValue("ProfileID", id);
                                command.Parameters.AddWithValue("Pattern", group.PatternId);

                                command.ExecuteNonQuery();
                            }
                        }
                    }

                    transaction.Commit();
                }
            }

            return id;
        }

        public int InsertNewFolderGroup(Profile profile, string name, string path, int pattern_id)
        {
            var query = @"INSERT INTO main.folder_root (name, path, profile_id, pattern_id) VALUES (@Name, @Path, @ProfileID, @Pattern)";
            var id = -1;

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("Name", name);
                    command.Parameters.AddWithValue("Path", path);
                    command.Parameters.AddWithValue("ProfileID", profile.Id);
                    command.Parameters.AddWithValue("Pattern", pattern_id);

                    connection.Open();

                    command.ExecuteNonQuery();

                    command.CommandText = @"SELECT MAX(root_id) FROM main.folder_root";

                    id = Convert.ToInt32(command.ExecuteScalar());
                }
            }

            return id;
        }

        public bool UpdateFolderGroup(FolderGroup group)
        {
            var query = @"UPDATE main.folder_root SET name = @Name, path = @Path, pattern_id = @Pattern WHERE root_id = @ID";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("Name", group.Name);
                    command.Parameters.AddWithValue("Path", group.Path);
                    command.Parameters.AddWithValue("Pattern", group.PatternId);
                    command.Parameters.AddWithValue("ID", group.Id);

                    connection.Open();

                    command.ExecuteNonQuery();
                }
            }

            return true;
        }

        public bool DeleteFolderGroup(FolderGroup folderGroup)
        {
            var query = string.Format("DELETE FROM main.file WHERE folder_root_id = {0}", folderGroup.Id);

            using(var connection = new SqliteConnection(ConnectionString))
            {
                connection.Open();

                using (var transaction = connection.BeginTransaction())
                {
                    using(var command = new SqliteCommand(query, connection, transaction))
                    {
                        command.ExecuteNonQuery();

                        command.CommandText = string.Format("DELETE FROM main.folder_root WHERE root_id = {0}", folderGroup.Id);

                        command.ExecuteNonQuery();
                    }

                    transaction.Commit();
                }
            }

            return true;
        }

        public bool DeleteProfile(Profile profile)
        {
            var query = string.Format("DELETE FROM main.file WHERE profile_id = {0}", profile.Id);

            using (var connection = new SqliteConnection(ConnectionString))
            {
                connection.Open();

                using (var transaction = connection.BeginTransaction())
                {
                    using (var command = new SqliteCommand(query, connection, transaction))
                    {
                        command.ExecuteNonQuery();

                        command.CommandText = string.Format("DELETE FROM main.folder_root WHERE profile_id = {0}", profile.Id);

                        command.ExecuteNonQuery();

                        command.CommandText = string.Format("DELETE FROM main.profile WHERE profile_id = {0}", profile.Id);

                        command.ExecuteNonQuery();
                    }

                    transaction.Commit();
                }
            }

            return true;
        }

        public List<Namingconvention> GetNamingConventions()
        {
            var conventions = new List<Namingconvention>();
            var query = @"SELECT name, example_text, pattern_id, regex FROM main.naming_pattern";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    connection.Open();

                    var reader = command.ExecuteReader();

                    while (reader.Read())
                    {
                        conventions.Add(new Namingconvention
                        {
                            Regex = reader["regex"].ToString(),
                            Name = reader["name"].ToString(),
                            ExampleText = reader["example_text"].ToString(),
                            Id = Convert.ToInt32(reader["pattern_id"])
                        });
                    }
                }
            }

            return conventions;
        }

        public string GetNamingPatternById(int id)
        {
            if (id == 0) return string.Empty;

            var query = @"SELECT regex FROM main.naming_pattern WHERE pattern_id = @ID";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("ID", id);
                    connection.Open();

                    var reader = command.ExecuteReader();

                    reader.Read();

                    return reader["regex"].ToString();
                }
            }
        }

        public string GetRootPathById(int id)
        {
            var query = @"SELECT path FROM folder_root WHERE root_id = @ID";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("ID", id);
                    connection.Open();

                    var reader = command.ExecuteReader();

                    reader.Read();

                    return reader["path"].ToString();
                }
            }
        }

        public bool UpdateNamingPatterById(int id, string newPattern)
        {
            var query = @"UPDATE main.naming_pattern SET regex = @pattern WHERE pattern_id = @ID";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("pattern", newPattern);
                    command.Parameters.AddWithValue("ID", id);
                    connection.Open();

                    command.ExecuteNonQuery();
                }
            }

            return true;
        }

        public List<FileSnapshot> GetFilesForProfileByFolderGroup(int profileId, int folderGroupId)
        {
            var files = new List<FileSnapshot>();

            var query = @"SELECT * FROM main.file INNER JOIN main.folder_root ON main.file.folder_root_id = main.folder_root.root_id WHERE main.file.profile_id = @ProfileId AND folder_root_id = @GroupId ORDER BY main.file.full_path ASC";

            using (var connection = new SqliteConnection(ConnectionString))
            {
                using (var command = new SqliteCommand(query, connection))
                {
                    command.Parameters.AddWithValue("ProfileId", profileId);
                    command.Parameters.AddWithValue("GroupId", folderGroupId);
                    connection.Open();

                    var reader = command.ExecuteReader();

                    while(reader.Read())
                    {
                        files.Add(new FileSnapshot
                        {
                            Id = Convert.ToInt32(reader["file_id"]),
                            PathNoRoot = reader["full_path"].ToString(),
                            Name = reader["name"].ToString(),
                            ProfileId = profileId,
                            RootFolderId = folderGroupId,
                            Size = Convert.ToInt64(reader["size"]),
                            CreationDate = Convert.ToDateTime(reader["date_created"]),
                            ModifiedDate = Convert.ToDateTime(reader["date_modified"]),
                            PatternId = Convert.ToInt32(reader["pattern_override_id"]),
                            RootPath = reader["path"].ToString(),
                        });
                    }
                }
            }

            return files;
        }

        public void InsertNewFileRecord(List<FileSnapshot> files, int profileId, int folderGroupId, string root)
        {
            var query = @"INSERT INTO main.file (full_path, name, size, date_created, date_modified, folder_root_id, profile_id, pattern_override_id) VALUES (@FullPath, @Name, @Size, @DateCreated, @DateModified, @FolderId, @ProfileId, @PatternId)";
            var rootLength = root.Length;

            using (var connection = new SqliteConnection(ConnectionString))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                {

                    using (var command = new SqliteCommand(query, connection, transaction))
                    {
                        foreach (var file in files)
                        {
                            command.Parameters.AddWithValue("FullPath", file.PathNoRoot.Remove(0, rootLength + 1)); //Remove the root and the trailing slash that the root folder would have.
                            command.Parameters.AddWithValue("Name", file.Name);
                            command.Parameters.AddWithValue("Size", file.Size);
                            command.Parameters.AddWithValue("DateCreated", file.CreationDate);
                            command.Parameters.AddWithValue("DateModified", file.ModifiedDate);
                            command.Parameters.AddWithValue("FolderId", folderGroupId);
                            command.Parameters.AddWithValue("ProfileId", profileId);
                            command.Parameters.AddWithValue("PatternId", file.PatternId);

                            command.ExecuteNonQuery();

                            command.Parameters.Clear();
                        }
                    }

                    transaction.Commit();
                }
            }
        }

        public struct Profile
        {
            public string Name;
            public string DefaultNewContentFolder;
            public string MachineName;
            public int Id;
            public List<FolderGroup> FolderGroups;
        }

        public struct FolderGroup
        {
            public int Id;
            public string Name;
            public string Path;
            public OrganizationType Type;
            public int PatternId;
        }

        public struct Namingconvention
        {
            public int Id;
            public string Name;
            public string ExampleText;
            public string Regex;
        }

        public struct FileSnapshot
        {
            public int Id;
            public string PathNoRoot;
            public string Name;
            public int ProfileId;
            public int PatternId;
            public int RootFolderId;
            public DateTime CreationDate;
            public DateTime ModifiedDate;
            public long Size;
            public string RootPath;
        }

        public enum OrganizationType
        {
            Flat = 0,
            SingleLevel = 1,
            Custom = 2
        }
    }
}
