using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using FluentMigrator;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Parser;

namespace NzbDrone.Core.Datastore.Migration
{
    [Migration(208)]
    public class collections : NzbDroneMigrationBase
    {
        protected override void MainDbUpgrade()
        {
            Create.TableForModel("Collections")
                .WithColumn("TmdbId").AsInt32().Unique()
                .WithColumn("QualityProfileId").AsInt32()
                .WithColumn("RootFolderPath").AsString()
                .WithColumn("MinimumAvailability").AsInt32()
                .WithColumn("SearchOnAdd").AsBoolean()
                .WithColumn("Title").AsString()
                .WithColumn("SortTitle").AsString().Nullable()
                .WithColumn("CleanTitle").AsString()
                .WithColumn("Overview").AsString().Nullable()
                .WithColumn("Images").AsString().WithDefaultValue("[]")
                .WithColumn("Monitored").AsBoolean().WithDefaultValue(false)
                .WithColumn("LastInfoSync").AsDateTime().Nullable()
                .WithColumn("Added").AsDateTime().Nullable();

            Alter.Table("MovieMetadata").AddColumn("CollectionTmdbId").AsInt32().Nullable()
                                        .AddColumn("CollectionTitle").AsString().Nullable();

            Alter.Table("ImportLists").AddColumn("Monitor").AsInt32().Nullable();

            Execute.WithConnection(MigrateCollections);
            Execute.WithConnection(MigrateCollectionMonitorStatus);
            Execute.WithConnection(MapCollections);
            Execute.WithConnection(MigrateListMonitor);

            Alter.Table("ImportLists").AlterColumn("Monitor").AsInt32().NotNullable();

            Delete.Column("ShouldMonitor").FromTable("ImportLists");
            Delete.FromTable("ImportLists").Row(new { Implementation = "TMDbCollectionImport" });
            Delete.Column("Collection").FromTable("MovieMetadata");
        }

        private void MigrateCollections(IDbConnection conn, IDbTransaction tran)
        {
            var rootPaths = new List<string>();
            using (var getRootFolders = conn.CreateCommand())
            {
                getRootFolders.Transaction = tran;
                getRootFolders.CommandText = @"SELECT ""Path"" FROM ""RootFolders""";

                using (var definitionsReader = getRootFolders.ExecuteReader())
                {
                    while (definitionsReader.Read())
                    {
                        string path = definitionsReader.GetString(0);
                        rootPaths.Add(path);
                    }
                }
            }

            var newCollections = new List<MovieCollection208>();
            using (var cmd = conn.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandText = "SELECT \"Collection\", \"ProfileId\", \"MinimumAvailability\", \"Path\" FROM \"Movies\" JOIN \"MovieMetadata\" ON \"Movies\".\"MovieMetadataId\" = \"MovieMetadata\".\"Id\" WHERE \"Collection\" IS NOT NULL";

                var addedCollections = new List<int>();
                var added = DateTime.UtcNow;

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var collection = reader.GetString(0);
                        var qualityProfileId = reader.GetInt32(1);
                        var minimumAvailability = reader.GetInt32(2);
                        var moviePath = reader.GetString(3);
                        var data = STJson.Deserialize<MovieCollection207>(collection);

                        if (data.TmdbId == 0 || newCollections.Any(d => d.TmdbId == data.TmdbId))
                        {
                            continue;
                        }

                        var rootFolderPath = rootPaths.Where(r => r.IsParentPath(moviePath))
                                          .OrderByDescending(r => r.Length)
                                          .FirstOrDefault();

                        if (rootFolderPath == null)
                        {
                            rootFolderPath = rootPaths.FirstOrDefault();
                        }

                        if (rootFolderPath == null)
                        {
                            rootFolderPath = moviePath.GetParentPath();
                        }

                        var collectionName = data.Name ?? $"Collection {data.TmdbId}";

                        newCollections.Add(new MovieCollection208
                        {
                            TmdbId = data.TmdbId,
                            Title = collectionName,
                            CleanTitle = collectionName.CleanMovieTitle(),
                            SortTitle = Parser.Parser.NormalizeTitle(collectionName),
                            Added = added,
                            QualityProfileId = qualityProfileId,
                            RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
                            SearchOnAdd = true,
                            MinimumAvailability = minimumAvailability
                        });
                    }
                }
            }

            var updateSql = "INSERT INTO \"Collections\" (\"TmdbId\", \"Title\", \"CleanTitle\", \"SortTitle\", \"Added\", \"QualityProfileId\", \"RootFolderPath\", \"SearchOnAdd\", \"MinimumAvailability\") VALUES (@TmdbId, @Title, @CleanTitle, @SortTitle, @Added, @QualityProfileId, @RootFolderPath, @SearchOnAdd, @MinimumAvailability)";
            conn.Execute(updateSql, newCollections, transaction: tran);
        }

        private void MigrateCollectionMonitorStatus(IDbConnection conn, IDbTransaction tran)
        {
            var updatedCollections = new List<MovieCollection208>();
            using (var cmd = conn.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandText = "SELECT \"Enabled\", \"EnableAuto\", \"Settings\", \"ShouldMonitor\", \"Id\" FROM \"ImportLists\" WHERE \"Implementation\" = 'TMDbCollectionImport'";

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var enabled = reader.GetBoolean(0);
                        var enabledAutoAdd = reader.GetBoolean(1);
                        var settings = reader.GetString(2);
                        var shouldMonitor = reader.GetBoolean(3);
                        var listId = reader.GetInt32(4);
                        var data = STJson.Deserialize<TmdbCollectionSettings206>(settings);

                        if (!enabled || !enabledAutoAdd || !int.TryParse(data.CollectionId, out var collectionId))
                        {
                            continue;
                        }

                        updatedCollections.Add(new MovieCollection208
                        {
                            TmdbId = collectionId,
                            Monitored = true
                        });
                    }
                }
            }

            var updateSql = "UPDATE \"Collections\" SET \"Monitored\" = @Monitored WHERE \"TmdbId\" = @TmdbId";
            conn.Execute(updateSql, updatedCollections, transaction: tran);
        }

        private void MigrateListMonitor(IDbConnection conn, IDbTransaction tran)
        {
            var updatedLists = new List<ImportList208>();
            using (var cmd = conn.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandText = "SELECT \"ShouldMonitor\", \"Id\" FROM \"ImportLists\"";

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var shouldMonitor = reader.GetBoolean(0);
                        var listId = reader.GetInt32(1);

                        updatedLists.Add(new ImportList208
                        {
                            Monitor = shouldMonitor ? 0 : 2,
                            Id = listId
                        });
                    }
                }
            }

            var updateSql = "UPDATE \"ImportLists\" SET \"Monitor\" = @Monitor WHERE \"Id\" = @Id";
            conn.Execute(updateSql, updatedLists, transaction: tran);
        }

        private void MapCollections(IDbConnection conn, IDbTransaction tran)
        {
            var updatedMeta = new List<MovieMetadata208>();

            using (var cmd = conn.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandText = "SELECT \"Id\", \"Collection\" FROM \"MovieMetadata\" WHERE \"Collection\" IS NOT NULL";

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var id = reader.GetInt32(0);
                        var collection = reader.GetString(1);
                        var data = STJson.Deserialize<MovieCollection207>(collection);

                        var collectionId = data.TmdbId;
                        var collectionTitle = data.Name;

                        updatedMeta.Add(new MovieMetadata208
                        {
                            CollectionTitle = collectionTitle,
                            CollectionTmdbId = collectionId,
                            Id = id
                        });
                    }
                }
            }

            var updateSql = "UPDATE \"MovieMetadata\" SET \"CollectionTmdbId\" = @CollectionTmdbId, \"CollectionTitle\" = @CollectionTitle WHERE \"Id\" = @Id";
            conn.Execute(updateSql, updatedMeta, transaction: tran);
        }

        private class MovieCollection207
        {
            public string Name { get; set; }
            public int TmdbId { get; set; }
        }

        private class MovieCollection208
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public string CleanTitle { get; set; }
            public string SortTitle { get; set; }
            public DateTime Added { get; set; }
            public int QualityProfileId { get; set; }
            public string RootFolderPath { get; set; }
            public bool SearchOnAdd { get; set; }
            public int MinimumAvailability { get; set; }
            public bool Monitored { get; set; }
            public int TmdbId { get; set; }
        }

        private class MovieMetadata208
        {
            public int Id { get; set; }
            public int CollectionTmdbId { get; set; }
            public string CollectionTitle { get; set; }
        }

        private class ImportList208
        {
            public int Id { get; set; }
            public int Monitor { get; set; }
        }

        private class TmdbCollectionSettings206
        {
            public string CollectionId { get; set; }
        }
    }
}
