using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.member;
using Umbraco.Core;
using Umbraco.Core.Configuration;

namespace OurUmbraco.Wiki.BusinessLogic
{
    public class WikiFile
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Path { get; set; }
        public string FileType { get; set; }
        public Guid NodeVersion { get; set; }
        public int CreatedBy { get; set; }
        public int RemovedBy { get; set; }
        public int NodeId { get; set; }

        public DateTime CreateDate { get; set; }
        public bool Current { get; set; }
        public int Downloads { get; set; }
        public bool Archived { get; set; }

        public bool Verified { get; set; }
        public bool SupportsMediumTrust { get { return false; } }

        public List<UmbracoVersion> Versions { get; set; }
        public UmbracoVersion Version { get; set; }


        public void Delete()
        {
            if (File.Exists(HttpContext.Current.Server.MapPath(Path)))
                File.Delete(HttpContext.Current.Server.MapPath(Path));

            Application.SqlHelper.ExecuteNonQuery("DELETE FROM wikiFiles where ID = @id", Application.SqlHelper.CreateParameter("@id", Id));
        }

        /// <summary>
        /// Gets all wiki files for all nodes
        /// </summary>
        /// <returns></returns>
        public static Dictionary<int, IEnumerable<WikiFile>> CurrentFiles(IEnumerable<int> nodeIds)
        {
            var wikiFiles = new Dictionary<int, List<WikiFile>>();

            //we can only have 2000 (actually 2100) SQL parameters used at once, so we need to group them
            var nodeBatches = nodeIds.InGroupsOf(2000);

            foreach (var nodeBatch in nodeBatches)
            {
                foreach (var result in ApplicationContext.Current.DatabaseContext.Database.Query<dynamic>("SELECT * FROM wikiFiles WHERE nodeId IN (@nodeIds)", new { nodeIds = nodeBatch }))
                {
                    var file = new WikiFile
                    {
                        Id = result.id,
                        Path = result.path,
                        Name = result.name,
                        FileType = result.type,
                        RemovedBy = result.removedBy,
                        CreatedBy = result.createdBy,
                        NodeVersion = result.version,
                        NodeId = result.nodeId,
                        CreateDate = result.createDate,
                        DotNetVersion = result.dotNetVersion,
                        Downloads = result.downloads,
                        Archived = result.archived,
                        Verified = result.verified,
                        Versions = GetVersionsFromString(result.umbracoVersion)
                    };

                    file.Version = file.Versions.Any()
                        ? GetVersionsFromString(result.umbracoVersion)[0]
                        : UmbracoVersion.DefaultVersion();

                    if (wikiFiles.ContainsKey(result.nodeId))
                    {
                        var list = wikiFiles[result.nodeId];
                        list.Add(file);
                    }
                    else
                    {
                        wikiFiles.Add(result.nodeId, new List<WikiFile>(new[] { file }));
                    }
                }
            }

            return wikiFiles.ToDictionary(x => x.Key, x => (IEnumerable<WikiFile>)x.Value);

        }

        public string DotNetVersion { get; set; }

        public static List<WikiFile> CurrentFiles(int nodeId)
        {
            var wikiFiles = new List<WikiFile>();

            using (var reader = Application.SqlHelper.ExecuteReader("SELECT id FROM wikiFiles WHERE nodeId = @nodeid", Application.SqlHelper.CreateParameter("@nodeId", nodeId)))
            {
                while (reader.Read())
                    wikiFiles.Add(new WikiFile(reader.GetInt("id")));

                return wikiFiles;
            }
        }
        
        private readonly Events _events = new Events();

        private WikiFile() { }

        public static WikiFile Create(string name, Guid node, Guid memberGuid, HttpPostedFile file, string filetype, List<UmbracoVersion> versions, string dotNetVersion)
        {
            try
            {
                var filename = file.FileName;
                var extension = filename.Substring(filename.LastIndexOf('.') + 1);

                if (ExtensionNotAllowed(extension))
                    return null;

                var content = Content.GetContentFromVersion(node);

                var member = new Member(memberGuid);

                if (content != null)
                {
                    var wikiFile = new WikiFile
                    {
                        Name = name,
                        NodeId = content.Id,
                        NodeVersion = content.Version,
                        FileType = filetype,
                        CreatedBy = member.Id,
                        Downloads = 0,
                        Archived = false,
                        Versions = versions,
                        Version = versions[0],
                        Verified = false,
                        DotNetVersion = dotNetVersion
                    };

                    var path = string.Format("/media/wiki/{0}", content.Id);

                    if (Directory.Exists(HttpContext.Current.Server.MapPath(path)) == false)
                        Directory.CreateDirectory(HttpContext.Current.Server.MapPath(path));

                    path = string.Format("{0}/{1}_{2}.{3}", path, DateTime.Now.Ticks, umbraco.cms.helpers.url.FormatUrl(filename), extension);

                    file.SaveAs(HttpContext.Current.Server.MapPath(path));

                    wikiFile.Path = path;

                    wikiFile.Save();

                    return wikiFile;
                }

            }
            catch (Exception ex)
            {
                Log.Add(LogTypes.Debug, -1, ex.ToString());
            }

            return null;
        }
        public static WikiFile Create(string name, Guid node, Guid memberGuid, HttpPostedFile file, string filetype, List<UmbracoVersion> versions)
        {
            return Create(name, node, memberGuid, file, filetype, versions, string.Empty);
        }

        private static bool ExtensionNotAllowed(string extension)
        {
            return UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(extension.ToLowerInvariant());
        }

        public static WikiFile Create(string fileName, string extension, Guid node, Guid memberGuid, byte[] file, string filetype, List<UmbracoVersion> versions)
        {
            try
            {
                if (ExtensionNotAllowed(extension))
                    return null;

                var content = Content.GetContentFromVersion(node);
                var member = new Member(memberGuid);

                if (content != null)
                {
                    var wikiFile = new WikiFile
                                   {
                                       Name = fileName,
                                       NodeId = content.Id,
                                       NodeVersion = content.Version,
                                       FileType = filetype,
                                       CreatedBy = member.Id,
                                       Downloads = 0,
                                       Archived = false,
                                       Versions = versions,
                                       Version = versions[0],
                                       Verified = false
                                   };

                    var path = string.Format("/media/wiki/{0}", content.Id);

                    if (Directory.Exists(HttpContext.Current.Server.MapPath(path)) == false)
                        Directory.CreateDirectory(HttpContext.Current.Server.MapPath(path));

                    fileName = fileName.Substring(0, fileName.LastIndexOf('.') + 1);
                    path = string.Format("{0}/{1}_{2}.{3}", path, DateTime.Now.Ticks, umbraco.cms.helpers.url.FormatUrl(fileName), extension);

                    using (var fileStream = new FileStream(HttpContext.Current.Server.MapPath(path), FileMode.Create))
                        fileStream.Write(file, 0, file.Length);

                    wikiFile.Path = path;
                    wikiFile.Save();

                    return wikiFile;
                }
            }
            catch (Exception ex)
            {
                Log.Add(LogTypes.Debug, -1, ex.ToString());
            }

            return null;
        }

        public void Save()
        {
            if (Id == 0)
            {
                var e = new FileCreateEventArgs();
                FireBeforeCreate(e);
                if (e.Cancel)
                    return;

                Application.SqlHelper.ExecuteNonQuery(
                    "INSERT INTO wikiFiles (path, name, createdBy, nodeId, version, type, downloads, archived, umbracoVersion, verified, dotNetVersion) VALUES(@path, @name, @createdBy, @nodeId, @nodeVersion, @type, @downloads, @archived, @umbracoVersion, @verified, @dotNetVersion)",
                    Application.SqlHelper.CreateParameter("@path", Path),
                    Application.SqlHelper.CreateParameter("@name", Name),
                    Application.SqlHelper.CreateParameter("@createdBy", CreatedBy),
                    Application.SqlHelper.CreateParameter("@nodeId", NodeId),
                    Application.SqlHelper.CreateParameter("@type", FileType),
                    Application.SqlHelper.CreateParameter("@nodeVersion", NodeVersion),
                    Application.SqlHelper.CreateParameter("@downloads", Downloads),
                    Application.SqlHelper.CreateParameter("@archived", Archived),
                    Application.SqlHelper.CreateParameter("@umbracoVersion", ToVersionString(Versions)),
                    Application.SqlHelper.CreateParameter("@verified", Verified),
                    Application.SqlHelper.CreateParameter("@dotNetVersion", DotNetVersion)
                    );

                CreateDate = DateTime.Now;

                Id = Application.SqlHelper.ExecuteScalar<int>("SELECT MAX(id) FROM wikiFiles WHERE createdBy = @createdBy", Application.SqlHelper.CreateParameter("@createdBy", CreatedBy));

                FireAfterCreate(e);
            }
            else
            {
                var e = new FileUpdateEventArgs();
                FireBeforeUpdate(e);

                if (e.Cancel)
                    return;

                Application.SqlHelper.ExecuteNonQuery(
                    "UPDATE wikiFiles SET path = @path, name = @name, type = @type, [current] = @current, removedBy = @removedBy, version = @version, downloads = @downloads, archived = @archived, umbracoVersion = @umbracoVersion, verified = @verified, dotNetVersion = @dotNetVersion WHERE id = @id",
                    Application.SqlHelper.CreateParameter("@path", Path),
                    Application.SqlHelper.CreateParameter("@name", Name),
                    Application.SqlHelper.CreateParameter("@type", FileType),
                    Application.SqlHelper.CreateParameter("@current", Current),
                    Application.SqlHelper.CreateParameter("@removedBy", RemovedBy),
                    Application.SqlHelper.CreateParameter("@version", NodeVersion),
                    Application.SqlHelper.CreateParameter("@id", Id),
                    Application.SqlHelper.CreateParameter("@downloads", Downloads),
                    Application.SqlHelper.CreateParameter("@archived", Archived),
                    Application.SqlHelper.CreateParameter("@umbracoVersion", ToVersionString(Versions)),
                    Application.SqlHelper.CreateParameter("@verified", Verified),
                    Application.SqlHelper.CreateParameter("@dotNetVersion", DotNetVersion)
                    );

                FireAfterUpdate(e);
            }
        }



        //available umbraco version strings: v4, v45, v3
        public static WikiFile FindPackageForUmbracoVersion(int nodeid, string umbracoVersion)
        {
            return FindRelatedFileForUmbracoVersion(nodeid, umbracoVersion, "package");
        }

        public static WikiFile FindPackageDocumentationForUmbracoVersion(int nodeid, string umbracoVersion)
        {
            return FindRelatedFileForUmbracoVersion(nodeid, umbracoVersion, "docs");
        }

        private static WikiFile FindRelatedFileForUmbracoVersion(int nodeid, string umbracoVersion, string fileType)
        {
            try
            {
                //try find one based on the specific version first
                var id = Application.SqlHelper.ExecuteScalar<int>(
                    "Select TOP 1 id from wikiFiles where nodeId = @nodeid and type = @packageType and (umbracoVersion like @umbracoVersion) ORDER BY createDate DESC",
                    Application.SqlHelper.CreateParameter("@nodeid", nodeid),
                    Application.SqlHelper.CreateParameter("@packageType", fileType),
                    Application.SqlHelper.CreateParameter("@umbracoVersion", "%" + umbracoVersion + "%")
                    );

                if (id > 0)
                    return new WikiFile(id);

                //if a version specific file wasnt found try and find one based on nan
                id = Application.SqlHelper.ExecuteScalar<int>(
                    "Select TOP 1 id from wikiFiles where nodeId = @nodeid and type = @packageType and (umbracoVersion like '%nan%') ORDER BY createDate DESC",
                    Application.SqlHelper.CreateParameter("@nodeid", nodeid),
                    Application.SqlHelper.CreateParameter("@packageType", fileType)
                    );

                if (id > 0)
                    return new WikiFile(id);
            }
            catch
            {
                return null;
            }

            return null;
        }

        public WikiFile(int id)
        {
            using (var reader = Application.SqlHelper.ExecuteReader("SELECT * FROM wikiFiles WHERE id = @fileId",
                    Application.SqlHelper.CreateParameter("@fileId", id)))
            {
                if (reader.Read())
                {
                    Id = reader.GetInt("id");
                    Path = reader.GetString("path");
                    Name = reader.GetString("name");
                    FileType = reader.GetString("type");
                    RemovedBy = reader.GetInt("removedBy");
                    CreatedBy = reader.GetInt("createdBy");
                    NodeVersion = reader.GetGuid("version");
                    NodeId = reader.GetInt("nodeId");
                    CreateDate = reader.GetDateTime("createDate");
                    Current = reader.GetBoolean("current");
                    Downloads = reader.GetInt("downloads");
                    Archived = reader.GetBoolean("archived");
                    Verified = reader.GetBoolean("verified");
                    DotNetVersion = reader.GetString("dotNetVersion");
                    Versions = GetVersionsFromString(reader.GetString("umbracoVersion"));
                    Version = Versions.Any()
                        ? GetVersionsFromString(reader.GetString("umbracoVersion"))[0]
                        : UmbracoVersion.DefaultVersion();
                }
                else
                {
                    throw new ArgumentException(string.Format("No node exists with id '{0}'", Id));
                }
            }
        }

        public void UpdateDownloadCounter()
        {
            UpdateDownloadCount(this.Id, false, true);
        }

        public void UpdateDownloadCounter(bool ignoreCookies, bool isPackage)
        {
            UpdateDownloadCount(this.Id, ignoreCookies, isPackage);
        }

        public static List<UmbracoVersion> GetVersionsFromString(string p)
        {
            var umbracoVersions = new List<UmbracoVersion>();

            foreach (var ver in p.Split(','))
            {
                if (UmbracoVersion.AvailableVersions().ContainsKey(ver))
                    umbracoVersions.Add(UmbracoVersion.AvailableVersions()[ver]);
            }

            return umbracoVersions;
        }

        public static string ToVersionString(List<UmbracoVersion> versions)
        {
            var umbracoVersions = string.Empty;

            foreach (var ver in versions)
                umbracoVersions += string.Format("{0},", ver.Version);

            return umbracoVersions.TrimEnd(',');

        }

        public void UpdateDownloadCount(int fileId, bool ignoreCookies, bool isPackage)
        {
            var cookie = HttpContext.Current.Request.Cookies["ProjectFileDownload" + fileId];

            if (cookie != null && ignoreCookies == false)
                return;

            var downloads = 0;
            var projectId = 0;

            var reader = Application.SqlHelper.ExecuteReader("Select downloads, nodeId from wikiFiles where id = @id;", Application.SqlHelper.CreateParameter("@id", fileId));
            if (reader.Read())
            {
                downloads = reader.GetInt("downloads");
                projectId = reader.GetInt("nodeId");
            }
            downloads = downloads + 1;

            Application.SqlHelper.ExecuteNonQuery(
                "update wikiFiles set downloads = @downloads where id = @id;",
                Application.SqlHelper.CreateParameter("@id", fileId),
                Application.SqlHelper.CreateParameter("@downloads", downloads));

            var totalDownloads = Application.SqlHelper.ExecuteScalar<int>("Select SUM(downloads) from wikiFiles where nodeId = @projectId;", Application.SqlHelper.CreateParameter("@projectId", projectId));
            
            if (isPackage)
            {
                var memberHelper = new Umbraco.Web.Security.MembershipHelper(Umbraco.Web.UmbracoContext.Current);
                var memberId = memberHelper.GetCurrentMemberId();
                var currentMemberId = memberId == -1 ? 0 : memberId;

                //update download count update
                Application.SqlHelper.ExecuteNonQuery(
                    @"insert into projectDownload(projectId,memberId,timestamp) 
                        values((select nodeId from wikiFiles where id = @id) ,@memberId, getdate())",
                    Application.SqlHelper.CreateParameter("@id", fileId),
                    Application.SqlHelper.CreateParameter("@memberId", currentMemberId));
            }

            var e = new FileDownloadUpdateEventArgs { ProjectId = projectId, Downloads = totalDownloads };
            FireAfterDownloadUpdate(e);

            cookie = new HttpCookie("ProjectFileDownload" + fileId) { Expires = DateTime.Now.AddHours(1) };
            HttpContext.Current.Response.Cookies.Add(cookie);
        }

        public byte[] ToByteArray()
        {
            var path = HttpContext.Current.Server.MapPath(this.Path);

            byte[] packageByteArray;

            using (var fileStream = File.Open(path, FileMode.Open, FileAccess.Read))
            {
                packageByteArray = new byte[fileStream.Length];

                fileStream.Read(packageByteArray, 0, (int)fileStream.Length);
            }

            return packageByteArray;
        }

        public XmlNode ToXml(XmlDocument d)
        {
            XmlNode toXml = d.CreateElement("wikiFile");

            toXml.Attributes.Append(umbraco.xmlHelper.addAttribute(d, "id", Id.ToString()));
            toXml.Attributes.Append(umbraco.xmlHelper.addAttribute(d, "name", Name));
            toXml.Attributes.Append(umbraco.xmlHelper.addAttribute(d, "created", CreateDate.ToString()));

            toXml.AppendChild(umbraco.xmlHelper.addCDataNode(d, "path", Path));
            toXml.AppendChild(umbraco.xmlHelper.addCDataNode(d, "verified", Verified.ToString()));

            return toXml;
        }

        /* EVENTS */
        public static event EventHandler<FileCreateEventArgs> BeforeCreate;
        protected virtual void FireBeforeCreate(FileCreateEventArgs e)
        {
            _events.FireCancelableEvent(BeforeCreate, this, e);
        }
        public static event EventHandler<FileCreateEventArgs> AfterCreate;
        protected virtual void FireAfterCreate(FileCreateEventArgs e)
        {
            if (AfterCreate != null)
                AfterCreate(this, e);
        }

        public static event EventHandler<FileRemoveEventArgs> BeforeRemove;
        protected virtual void FireBeforeDelete(FileRemoveEventArgs e)
        {
            _events.FireCancelableEvent(BeforeRemove, this, e);
        }
        public static event EventHandler<FileRemoveEventArgs> AfterRemove;
        protected virtual void FireAfterDelete(FileRemoveEventArgs e)
        {
            if (AfterRemove != null)
                AfterRemove(this, e);
        }

        public static event EventHandler<FileUpdateEventArgs> BeforeUpdate;
        protected virtual void FireBeforeUpdate(FileUpdateEventArgs e)
        {
            _events.FireCancelableEvent(BeforeUpdate, this, e);
        }
        public static event EventHandler<FileUpdateEventArgs> AfterUpdate;
        protected virtual void FireAfterUpdate(FileUpdateEventArgs e)
        {
            if (AfterUpdate != null)
                AfterUpdate(this, e);
        }

        public static event EventHandler<FileDownloadUpdateEventArgs> AfterDownloadUpdate;
        protected virtual void FireAfterDownloadUpdate(FileDownloadUpdateEventArgs e)
        {
            if (AfterDownloadUpdate != null)
                AfterDownloadUpdate(this, e);
        }
    }
}
