
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NuGet.Packaging.Core;
using NuGet.Versioning;
using isnd.Data;
using isnd.Helpers;
using isnd.Entities;
using Microsoft.AspNetCore.Http;
using isn.abst;
using isnd.Data.Packages;
using Microsoft.AspNetCore.Authorization;

namespace isnd.Controllers
{

    public partial class PackagesController
    {
       // TODO [Authorize(Policy = IsndConstants.RequireValidApiKey)]
        [HttpPut("~" + Constants.ApiVersion + ApiConfig.Package)]
        public async Task<IActionResult> Put()
        {
            try
            {
                var clientVersionId = Request.Headers["X-NuGet-Client-Version"];
                string apiKey = Request.Headers["X-NuGet-ApiKey"][0];
                ViewData["versionId"] = typeof(PackagesController).Assembly.FullName;
                var files = new List<string>();
                ViewData["files"] = files;

                var clearkey = protector.Unprotect(apiKey);
                var apikey = dbContext.ApiKeys.SingleOrDefault(k => k.Id == clearkey);
                if (apikey == null)
                {
                    logger.LogError("403 : no api-key");
                    return Unauthorized();
                }
                Commit commit = new Commit
                {
                    Action = PackageAction.PublishPackage,
                    TimeStamp = DateTime.Now
                };
                dbContext.Commits.Add(commit);

                foreach (IFormFile file in Request.Form.Files)
                {
                    string initpath = Path.Combine(Environment.GetEnvironmentVariable("TEMP") ??
                    Environment.GetEnvironmentVariable("TMP") ?? "/tmp",
                     $"isn-{Guid.NewGuid()}."+Constants.PaquetFileEstension);

                    using (FileStream fw = new FileStream(initpath, FileMode.Create))
                    {
                        file.CopyTo(fw);
                    }

                    using (FileStream fw = new FileStream(initpath, FileMode.Open))
                    {
                        var archive = new ZipArchive(fw);

                        var spec = archive.Entries.FirstOrDefault(e => e.FullName.EndsWith("." + Constants.SpecFileEstension));
                        if (spec == null) return BadRequest(new { error = "no " + Constants.SpecFileEstension + " from archive" });
                        string pkgpath;
                        NuGetVersion version;
                        string pkgid;
                        string fullpath;

                        using (var specstr = spec.Open())
                        {
                            NuspecCoreReader reader = new NuspecCoreReader(specstr);

                            string pkgdesc = reader.GetDescription();
                            var types = reader.GetPackageTypes();
                            pkgid = reader.GetId();
                            version = reader.GetVersion();

                            string pkgidpath = Path.Combine(isndSettings.PackagesRootDir,
                                pkgid);
                            pkgpath = Path.Combine(pkgidpath, version.ToFullString());
                            string name = $"{pkgid}-{version}."+Constants.PaquetFileEstension;
                            fullpath = Path.Combine(pkgpath, name);

                            var destpkgiddir = new DirectoryInfo(pkgidpath);
                            Package pkg = dbContext.Packages.SingleOrDefault(p => p.Id == pkgid);
                            if (pkg != null)
                            {
                                if (pkg.OwnerId != apikey.UserId)
                                {
                                    return new ForbidResult();
                                }
                                pkg.Description = pkgdesc;
                            }
                            else
                            {
                                pkg = new Package
                                {
                                    Id = pkgid,
                                    Description = pkgdesc,
                                    OwnerId = apikey.UserId,
                                    LatestVersion = commit,
                                };
                                dbContext.Packages.Add(pkg);
                            }
                            if (!destpkgiddir.Exists) destpkgiddir.Create();

                            var source = new FileInfo(initpath);
                            var dest = new FileInfo(fullpath);
                            var destdir = new DirectoryInfo(dest.DirectoryName);
                            if (dest.Exists)
                            {
                                // La version existe sur le disque,
                                // mais si elle ne l'est pas en base de donnéés,
                                // on remplace la version sur disque.
                                var pkgv = dbContext.PackageVersions.Where(
                                    v => v.PackageId == pkg.Id 
                                );
                                
                                if (pkgv !=null && pkgv.Count()==0) 
                                {
                                    dest.Delete();
                                }
                                else {
                                    logger.LogWarning("400 : pkgversion:existant");
                                    ModelState.AddModelError("pkgversion", "existant" );
                                    return BadRequest(ModelState);
                                }
                            }
                            {
                                if (!destdir.Exists) destdir.Create();
                                source.MoveTo(fullpath);
                                files.Add(name);
                                string fullstringversion = version.ToFullString();
                                var pkgvers = dbContext.PackageVersions.Where
                                (v => v.PackageId == pkg.Id && v.FullString == fullstringversion);
                                if (pkgvers.Count() > 0)
                                {
                                    foreach (var v in pkgvers.ToArray())
                                        dbContext.PackageVersions.Remove(v);
                                }
                                
                                // FIXME default type or null
                                if (types==null || types.Count==0)
                                dbContext.PackageVersions.Add
                                (new PackageVersion{

                                        Package = pkg,
                                        Major = version.Major,
                                        Minor = version.Minor,
                                        Patch = version.Patch,
                                        Revision = version.Revision,
                                        IsPrerelease = version.IsPrerelease,
                                        FullString = version.ToFullString(),
                                        Type = null,
                                        LatestCommit = commit
                                });
                                else
                                foreach (var type in types)
                                {
                                    var pkgver = new PackageVersion
                                    {
                                        Package = pkg,
                                        Major = version.Major,
                                        Minor = version.Minor,
                                        Patch = version.Patch,
                                        IsPrerelease = version.IsPrerelease,
                                        FullString = version.ToFullString(),
                                        Type = type.Name,
                                        LatestCommit = commit
                                    };
                                    dbContext.PackageVersions.Add(pkgver);
                                }

                                await dbContext.SaveChangesAsync();
                                packageManager.ÛpdateCatalogForAsync(commit);

                                logger.LogInformation($"new paquet : {spec.Name}");
                            }
                        }
                        using (var shacrypto = System.Security.Cryptography.SHA512.Create())
                        {
                            using (var stream = System.IO.File.OpenRead(fullpath))
                            {
                                var hash = shacrypto.ComputeHash(stream);
                                var shafullname = fullpath + ".sha512";
                                var hashtext = Convert.ToBase64String(hash);
                                var hashtextbytes = Encoding.ASCII.GetBytes(hashtext);

                                using (var shafile = System.IO.File.OpenWrite(shafullname))
                                {
                                    shafile.Write(hashtextbytes, 0, hashtextbytes.Length);
                                }
                            }
                        }
                        string nuspecfullpath = Path.Combine(pkgpath, pkgid + "." + Constants.SpecFileEstension);
                        FileInfo nfpi = new FileInfo(nuspecfullpath);

                        if (nfpi.Exists)
                           nfpi.Delete();
                        spec.ExtractToFile(nuspecfullpath);
                    }

                }
                return Ok(ViewData);
            }
            catch (Exception ex)
            {
                logger.LogError(ex.Message);
                logger.LogError("Stack Trace: " + ex.StackTrace);
                return new ObjectResult(new { ViewData, ex.Message })
                { StatusCode = 500 };
            }
        }
    }
}
