using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using isn.abst;
using isn.Abstract;
using isnd.Controllers;
using isnd.Data;
using isnd.Data.Catalog;
using isnd.Entities;
using isnd.Helpers;
using isnd.Interfaces;
using isnd.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using NuGet.Versioning;
using Unleash;

namespace isnd.Services
{
    public class PackageManager : IPackageManager
    {
        ApplicationDbContext dbContext;
        
        public PackageManager(ApplicationDbContext dbContext,
        IOptions<IsndSettings> siteConfigOptionsOptions)
        {
            this.dbContext = dbContext;
            isndSettings = siteConfigOptionsOptions.Value;
            extUrl = isndSettings.ExternalUrl + "/";
            CurrentCatalogIndex = GetCatalogIndex();
        }

        public IEnumerable<Resource> GetResources(IUnleash unleashClient)
        {

            var res = new List<Resource>();

            // stable
            if (unleashClient.IsEnabled("pkg-push", true))
                res.Add(
                    new Resource
                    {
                        Id = extUrl + ApiConfig.Publish,
                        Type = Constants.PublishCommandId,
                        Comment = "Package Publish service"
                    });
            // under dev, only leash in release mode
            if (unleashClient.IsEnabled("pkg-get", false))
                res.Add(
                    new Resource
                    {
                        Id = extUrl + ApiConfig.Base,
                        Type = "PackageBaseAddress/3.0.0",
                        Comment = "Package Base Address service"
                    });
            if (unleashClient.IsEnabled("pkg-autocomplete", false))
                res.Add(
                    new Resource
                    {
                        Id = extUrl + ApiConfig.AutoComplete,
                        Type = "SearchAutocompleteService/3.5.0",
                        Comment = "Auto complete service"
                    });
            if (unleashClient.IsEnabled("pkg-search", false))
                res.Add(
                    new Resource
                    {
                        Id = extUrl + ApiConfig.Search,
                        Type = "SearchQueryService/3.5.0",
                        Comment = "Search Query service"
                    });

            if (unleashClient.IsEnabled("pkg-catalog", false))
                res.Add(
                    new Resource
                    {
                        Id = extUrl + ApiConfig.Catalog,
                        Type = "Catalog/3.0.0",
                        Comment = "Package Catalog Index"
                    });
            return res;

        }

        public PackageIndexViewModel SearchByName(string query,
             int skip, int take, bool prerelease = false,
             string packageType = null)
        {

            var scope = dbContext.Packages
                .Include(p => p.Versions)
                .Where(
                    p => (PackageIdHelpers.CamelCaseMatch(p.Id, query) || PackageIdHelpers.SeparatedByMinusMatch(p.Id, query))
                    && (prerelease || p.Versions.Any(v => !v.IsPrerelease))
                    && (packageType == null || p.Versions.Any(v => v.Type == packageType))
                );
            var total = scope.Count();
            var pkgs = scope.Skip(skip).Take(take).ToArray();

            return new PackageIndexViewModel
            {
                Query = query,
                TotalHits = total,
                Data = pkgs
            };
        }
        public AutoCompleteResult AutoComplete(string id,
             int skip, int take, bool prerelease = false,
             string packageType = null)
        {
            var scope = dbContext.PackageVersions.Where(
                         v => v.PackageId == id
                            && (prerelease || !v.IsPrerelease)
                            && (packageType == null || v.Type == packageType)
                     )
                     .OrderBy(v => v.FullString);
            return new AutoCompleteResult
            {
                totalHits = scope.Count(),
                data = scope.Select(v => v.FullString)
                     .Skip(skip).Take(take).ToArray()
            };
        }

        // TODO stocker MetaData plutôt que FullString en base,
        // et en profiter pour corriger ce listing
        public string[] GetVersions(
            string id,
            NuGetVersion parsedVersion,
            bool prerelease = false,
            string packageType = null,
            int skip = 0,
            int take = 25)
        {
            return dbContext.PackageVersions.Where(
                         v => v.PackageId == id
                            && (prerelease || !v.IsPrerelease)
                            && (packageType == null || v.Type == packageType)
                            && (parsedVersion.CompareTo(new SemanticVersion(v.Major, v.Minor, v.Patch)) < 0)
                     )
                     .OrderBy(v => v.FullString)
                     .Select(v => v.FullString)
                     .Skip(skip).Take(take).ToArray();
        }

        public static CatalogIndex CurrentCatalogIndex { get; protected set; }
        public static List<Page> CurrentCatalogPages { get; protected set; }
        private IsndSettings isndSettings;
        private string extUrl;

        public virtual CatalogIndex GetCatalogIndex()
        {
            if (CurrentCatalogIndex == null)
            {
                ÛpdateCatalogFor();
            }
            return CurrentCatalogIndex;
        }
        public void ÛpdateCatalogFor(Commit reason = null)
        {
            int i = 0;
            int p = 0;
            var oldIndex = CurrentCatalogIndex;
            var oldPages = CurrentCatalogPages;
            string baseid= extUrl + ApiConfig.Catalog;
            string basepageid= extUrl + ApiConfig.CatalogPage;
            CurrentCatalogIndex = new CatalogIndex
            {
                Id = baseid,
                Items = new List<PageRef>()
            };
            CurrentCatalogPages = new List<Page>();

            var scope = dbContext.Commits.OrderBy(c => c.TimeStamp);
            
            PageRef pageRef = null;
            Page page = null;
            i = isndSettings.CatalogPageLen;
            foreach (var commit in scope)
            {
                if (i >= this.isndSettings.CatalogPageLen)
                {
                    page = new Page
                    {
                        Id = basepageid + "-" + p++,
                        Parent = baseid,
                        CommitId = commit.CommitId,
                        CommitTimeStamp = commit.CommitTimeStamp,
                        Items = new List<PackageRef>()
                    };
                    CurrentCatalogPages.Add(page);
                    pageRef = new PageRef
                    {
                        Id = page.Id,
                        CommitId = commit.CommitId,
                        CommitTimeStamp = commit.CommitTimeStamp
                    };
                    CurrentCatalogIndex.Items.Add(pageRef);
                    i = 0;
                }
                var validPkgs = dbContext.Packages
                .Include(pkg => pkg.Versions)
                .Include(pkg => pkg.LatestVersion)
                .Where(
                    pkg => pkg.Versions.Count(v => v.CommitId == commit.CommitId) > 0
                );
                // pkg.Versions.OrderByDescending(vi => vi.CommitNId).First().FullString
                foreach (var pkg in validPkgs)
                {
                    var v = pkg.Versions.
                    Where (cv => cv.CommitId == commit.CommitId)
                    .OrderByDescending(vc => vc.CommitNId).First();

                    StringBuilder refid = new StringBuilder(extUrl);
                    refid.AppendFormat("{0}/{1}/{2}",ApiConfig.CatalogLeaf, v.PackageId
                    , v.FullString);
                    if (v.Type!=null)
                      refid.AppendFormat("/{0}",v.Type);

                    var pkgref = new PackageRef
                    {
                        Version = v.FullString,
                        LastCommit = v.LatestCommit,
                        CommitId = v.LatestCommit.CommitId,
                        CommitTimeStamp = v.LatestCommit.CommitTimeStamp,
                        RefId = refid.ToString(),
                        Id = v.PackageId,
                        RefType = v.LatestCommit.Action == PackageAction.PublishPackage
                        ? "nuget:PackageDetails" :
                        "nuget:PackageDelete"
                    };
                    page.Items.Add(pkgref);
                }
                reason = commit;
                i++;
            }

            if (reason != null)
            {
                CurrentCatalogIndex.CommitId = reason.CommitId;
            }
            else
            {
                // From a fresh db
                CurrentCatalogIndex.CommitId = "none";
            }
        }

        public async Task<PackageDeletionReport> DeletePackageAsync(string pkgid, string version, string type)
        {
            // TODO package deletion on disk
            var commit = new Commit{
                Action = PackageAction.DeletePackage,
                TimeStamp = DateTime.Now
            };
            dbContext.Commits.Add(commit);
            var pkg = await dbContext.PackageVersions.SingleOrDefaultAsync(
                v => v.PackageId == pkgid &&
                v.FullString == version &&
                v.Type == type
            );
            if (pkg == null)
            {
                return new PackageDeletionReport{ Deleted = false };
            }
            dbContext.PackageVersions.Remove(pkg);
            await dbContext.SaveChangesAsync();
            ÛpdateCatalogFor(commit);
            return new PackageDeletionReport{ Deleted = true, DeletedVersion = pkg };
        }

        public async Task<PackageVersion> GetPackageAsync(string pkgid, string version, string type)
        {
            return await dbContext.PackageVersions.SingleOrDefaultAsync(
                v => v.PackageId == pkgid &&
                v.FullString == version &&
                v.Type == type
            );
        }
    }
}