using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using isn.abst;
using isn.Abstract;
using isnd.Data;
using isnd.Data.Catalog;
using isnd.Data.Packages;
using isnd.Entities;
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
    {
        public const string BASE_API_LEVEL = "3.0.0";
        readonly ApplicationDbContext dbContext;

        public PackageManager(ApplicationDbContext dbContext,
        IOptions<IsndSettings> siteConfigOptionsOptions)
        {
            this.dbContext = dbContext;
            isndSettings = siteConfigOptionsOptions.Value;
            apiBase = isndSettings.ExternalUrl + Constants.ApiVersion;
        }

        public IEnumerable<Resource> GetResources(IUnleash unleashClient)
        {
     
            var res = new List<Resource>
            {
                new Resource(apiBase + ApiConfig.Package, 
                "PackagePublish/2.0.0")
                {
                    Comment = "Package Publish service"
                },
                // under dev, only leash in release mode

                new Resource(apiBase + ApiConfig.Package + "/{id}/{version}", 
                "PackageDetailsUriTemplate/5.1.0")
                {
                    Comment = "URI template used by NuGet Client to construct details URL for packages"
                },
                
                new Resource(apiBase + ApiConfig.Nuget, 
                "PackageBaseAddress/3.0.0")
                {
                    Comment = "Package Base Address service - " +
                     "Base URL of where NuGet packages are stored, in the format " +
                     "https://<host>/nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"
                },

                new Resource(apiBase + ApiConfig.AutoComplete, 
                "SearchAutocompleteService/" + BASE_API_LEVEL)
                {
                    Comment = "Auto complete service"
                },

                new Resource(apiBase + ApiConfig.Search,
                "SearchQueryService/" + BASE_API_LEVEL)
                {
                    Comment = "Search Query service"
                },

                new Resource(apiBase +  ApiConfig.Registration,
                "RegistrationsBaseUrl/Versioned")
                {
                    Comment = "Base URL of storage where package registration info is stored. " +
                     "This base URL includes SemVer 2.0.0 packages."
                }
            };
           
            // TODO ? RegistrationsBaseUrl RegistrationsBaseUrl/v* Catalog

            return res;
        }

        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()
            };
        }

        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.NugetVersion)
                     .Select(v => v.FullString)
                     .Skip(skip).Take(take).ToArray();
        }



        public string CatalogBaseUrl => apiBase;

        private IsndSettings isndSettings;
        private readonly string apiBase;

        public virtual async Task<RegistrationPageIndex> GetCatalogIndexAsync()
        {
            return await ÛpdateCatalogForAsync(null);
        }

        public async Task<RegistrationPageIndex> ÛpdateCatalogForAsync
        (Commit reason = null)
        {
            int i = 0;

            string baseid = apiBase + ApiConfig.Catalog;
            string bidreg = $"{apiBase}v3.4.0/{ApiConfig.Registration}";
            RegistrationPageIndex index = new RegistrationPageIndex(baseid);

            var scope = await dbContext.Commits.OrderBy(c => c.TimeStamp).ToArrayAsync();


            i = isndSettings.CatalogPageLen;
            foreach (var commit in scope)
            {

                var validPkgs = (await dbContext.Packages
                .Include(po => po.Owner)
                .Include(pkg => pkg.Versions)
                .Include(pkg => pkg.LatestVersion)
                .ToArrayAsync())
                .GroupBy((q) => q.Id);
                
                foreach (var pkgid in validPkgs)
                {

                    RegistrationPage page = index.Items.FirstOrDefault
                    (p => p.GetPackageId() == pkgid.Key);
                    if (page == null)
                    {
                        page = new RegistrationPage(bidreg, pkgid.Key, apiBase);
                        index.Items.Add(page);
                    }

                    foreach (var pkgv in pkgid)
                    {
                        page.AddVersionRange(pkgv.Versions);
                    }
                }
                reason = commit;
                i++;
            }

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

        public async Task<PackageDeletionReport> DeletePackageAsync
        (string pkgid, string version, string type)
        {
            // TODO 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();
            await ÛpdateCatalogForAsync(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
            );
        }

        public async Task<CatalogEntry> GetCatalogEntryAsync
        (string pkgId, string semver = null, string pkgType = null)
        {
            return (await dbContext.PackageVersions
            .Include(v => v.Package)
            .Include(v => v.LatestCommit)
              .Where(v => v.PackageId == pkgId
              && semver == v.SementicVersionString
               && (pkgType == null || pkgType == v.Type)).SingleOrDefaultAsync()).ToLeave(BID,
               apiBase);
        }

        public async Task<PackageDeletionReport> UserAskForPackageDeletionAsync
        (string uid, string id, string lower, string type)
        {
            PackageVersion packageVersion = await dbContext.PackageVersions
             .Include(pv => pv.Package)
             .FirstOrDefaultAsync(m => m.PackageId == id
             && m.FullString == lower && m.Type == type);
            if (packageVersion == null) return null;
            if (packageVersion.Package.OwnerId != uid) return null;
            return new PackageDeletionReport { Deleted = true, DeletedVersion = packageVersion };
        }

        public string BID { get => $"{apiBase}v3.4.0/{ApiConfig.Registration}"; }

        public IEnumerable<CatalogEntry> SearchCatalogEntriesById
        (string pkgId, string semver, string pkgType)
        {
            
            return dbContext.PackageVersions
            .Include(v => v.Package)
            .Include(v => v.Package.Owner)
            .Include(v => v.LatestCommit)
              .Where(v => v.PackageId == pkgId && semver == v.FullString
               && (pkgType == null || pkgType == v.Type))
               .OrderByDescending(p => p.CommitNId)
               .Select(p => p.ToLeave(BID, apiBase))
               ;
        }
        public PackageVersion GetPackage(string pkgId, string semver, string pkgType)
        {
            return dbContext.PackageVersions
            .Include(v => v.Package)
            .Include(v => v.LatestCommit)
              .Single(v => v.PackageId == pkgId && semver == v.FullString
               && (pkgType == null || pkgType == v.Type));
        }

        public async Task<RegistrationPageIndex> GetPackageRegistrationIndexAsync
        (RegistrationPageIndexQuery query)
        {
            // RegistrationPageIndexAndQuery
            if (string.IsNullOrWhiteSpace(query.Query)) return null;
            query.Query = query.Query.ToLower();
            var scope = await dbContext.Packages.Include(p => p.Versions).Include(p => p.Owner)
            .Where(p => p.Id.ToLower() == query.Query).Skip(query.Skip).Take(query.Take).ToListAsync();

            string bid = $"{apiBase}v3.4.0/{ApiConfig.Registration}";
            return
                new RegistrationPageIndex(bid, query.Query, apiBase, scope);
        }
        public async Task<RegistrationPageIndex> SearchPackageAsync(RegistrationPageIndexQuery query)
        {
            string bid = $"{apiBase}v3.4.0/{ApiConfig.Registration}";

            if (query.Query == null) query.Query = "";
            var scope = (await dbContext.Packages.Include(p => p.Versions).Include(p => p.Owner)
            .ToListAsync())
              .Where(p => Matching(p, query))
                 ;
            var total = scope.Count();
            var pkgs = scope.Skip(query.Skip).Take(query.Take);

            return
                new RegistrationPageIndex(bid, query.Query, apiBase, pkgs);
        }

        private static bool MatchingExact(Package p, RegistrationPageIndexQuery query)
        {
            return
            p.Id == query.Query
                  && (query.Prerelease || p.Versions.Any(v => !v.IsPrerelease));
        }

        private static bool Matching(Package p, RegistrationPageIndexQuery query)
        {
            return p.Id.StartsWith(query.Query)
                  && (query.Prerelease || p.Versions.Any(v => !v.IsPrerelease));
        }
    }
}