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;

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.ApiVersionPrefix;
        }

        public IEnumerable<Resource> GetResources()
        {
     
            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.Content,
                "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.StartsWith(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 = null,
            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==null || 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<PackageRegistration> GetCatalogIndexAsync()
        {
            return await ÛpdateCatalogForAsync(null);
        }

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

            string baseid = apiBase + ApiConfig.Catalog;
            string bidreg = $"{apiBase}{ApiConfig.Registration}";
            PackageRegistration index = new PackageRegistration(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.LatestCommit)
                .ToArrayAsync())
                .GroupBy((q) => q.Id);
                
                foreach (var pkgid in validPkgs)
                {

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

                    foreach (var pkgv in pkgid)
                    {
                        page.AddVersionRange(pkgv.Versions);
                    }
                }
                reason = commit;
                i++;
            }
            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 = DateTimeOffset.Now.ToUniversalTime()
            };
            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<Data.Catalog.Package> GetCatalogEntryAsync
        (string pkgId, string semver = null, string pkgType = null)
        {
            return (await dbContext.PackageVersions
            .Include(v => v.Package).Include(v => v.Package.Owner)
              .Where(v => v.PackageId == pkgId 
              && v.FullString == semver).SingleOrDefaultAsync()).ToPackage(
               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 IEnumerable<Data.Catalog.Package> SearchCatalogEntriesById
        (string pkgId, string semver, string pkgType, bool preRelease)
        {
            
            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)
               && (preRelease || !v.IsPrerelease))
               .OrderByDescending(p => p.CommitNId)
               .Select(p => p.ToPackage(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<PackageRegistration> GetPackageRegistrationIndexAsync
        (PackageRegistrationQuery 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)
            .Include(p => p.LatestCommit)
            .SingleOrDefaultAsync(p => p.Id.ToLower() == query.Query);
            if (scope==null) return null;
            if (scope.Versions.Count==0) return null;
            string bid = $"{apiBase}{ApiConfig.Registration}";
            foreach (var version in scope.Versions)
                version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId);
            return
                new PackageRegistration(bid, apiBase, scope);
        }
        public async Task<IEnumerable<PackageRegistration>> SearchPackageAsync(PackageRegistrationQuery query)
        {
            string bid = $"{apiBase}{ApiConfig.Registration}";

            if (query.Query == null) query.Query = "";
            var scope = (await dbContext.Packages
            .Include(p => p.Owner)
            .Include(p => p.Versions)
            .Include(p => p.LatestCommit)
              .Where(p => p.Id.StartsWith(query.Query)
              && (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease)))
              .OrderBy(p => p.CommitNId)
              .Skip(query.Skip).Take(query.Take)
            .ToListAsync()
              );
            scope.ForEach(pkg =>
                pkg.Versions.ForEach (version =>
                    version.LatestCommit = dbContext.Commits.Single(c => c.Id == version.CommitNId))
            );

            return scope.Select(p => new PackageRegistration(bid, apiBase, p));
        }


    }
}