using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Siger.Middlelayer.Redis;
using Siger.Middlelayer.Redis.Repositories;
using Siger.Middlelayer.Repository;
using Siger.Middlelayer.Repository.Entities;
using Siger.Middlelayer.Repository.Repositories.Interface;

namespace Siger.ApiConfig.Controller
{
    public class FileSystemController : BaseController
    {
        const string filePartName = ".part-";

        public static object objLock = new object();
        public static object objMergeLock = new object();

        //public static ConcurrentDictionary<string, FileState> fileMapDict = new ConcurrentDictionary<string, FileState>();

        //public static ConcurrentDictionary<string, int> fileStatisticsDict = new ConcurrentDictionary<string, int>();

        //public static ConcurrentDictionary<string, ParamEnti> fileParamDict = new ConcurrentDictionary<string, ParamEnti>();

        IFsFileinfoRepository _repository;
        FileSystemRepository fileRedis;
        IUnitOfWork _unitOfWork;
        public FileSystemController(IUnitOfWork unitOfWork, IFsFileinfoRepository repository)
        {
            _unitOfWork = unitOfWork;
            _repository = repository;
            fileRedis = new FileSystemRepository();
        }

        /// <summary>
        /// {enti.fileName}_{enti.fileType}_{enti.lastModified}_{enti.totalSize} 这四个为必填字段
        /// </summary>
        /// <param name="enti"></param>
        /// <returns></returns>

        [HttpGet]
        public FileResult GetFileInfo([FromQuery] ParamEnti enti)
        {
            FileResult result = new FileResult();
            if (!string.IsNullOrEmpty(enti.fileName))
            {
                var fileTuple = GetMapFileId(enti, false);

                if (fileTuple.Item2)
                {
                    result.FileId = fileTuple.Item1.FileId;
                    var fileUploadPath = Path.Combine(FileOperateInfo.FileBasePath, fileTuple.Item1.FileId + fileTuple.Item1.FileType);
                    if (System.IO.File.Exists(fileUploadPath))
                    {
                        result.IsHasUploaded = true;
                    }
                    else
                    {
                        BreakUploadInfo breakInfo = new BreakUploadInfo();
                        var dirPath = Path.Combine(FileOperateInfo.FileBasePath, fileTuple.Item1.FileId);
                        var tempFiles = DirectoryHelper.GetDirectFileCounts(dirPath);
                        List<int> existList = new List<int>();
                        List<int> misList = new List<int>();
                        double buffer = enti.fileChunkSize;
                        int index = 1;
                        double position = 0;
                        while (position > enti.totalSize)
                        {
                            var fileActPartName = enti.fileName + filePartName + index.ToString();
                            if (tempFiles.Contains(fileActPartName))
                            {
                                existList.Add(index);
                            }
                            else
                            {
                                misList.Add(index);
                            }
                            index++;
                            position += buffer;
                        }
                        breakInfo.HadUploadItems = existList.ToArray();
                        breakInfo.NeedBreakItems = misList.ToArray();
                        result.FileChunkSize = fileTuple.Item1.FileChunkSize;
                        result.IsFileBreak = true;
                        result.BreakInfo = breakInfo;
                    }
                }
                result.FileName = enti.fileName;
            }
            return result;
        }

        [HttpPost]
        public FileResult Upload([FromQuery] ParamEnti enti)
        {
            try
            {
                var files = Request.Form.Files;
                if (files != null && files.Count == 1)
                {
                    var file = files[0];
                    var fileSplits = file.FileName.Split(filePartName);
                    string actFileName = "";
                    bool isPartUpload = false;
                    if (fileSplits.Length > 1)
                    {
                        isPartUpload = true;
                        actFileName = fileSplits[0];
                    }
                    else
                    {
                        actFileName = file.FileName;
                    }
                    enti.fileName = actFileName;
                    enti = FileOperateInfo.GetFileInfo(file, enti);                    
                    var fileTuple = GetMapFileId(enti);
                    var fileState = fileTuple.Item1;
                    var id = fileState.FileId;

                    FileResult result = new FileResult();
                    result.FileId = id;
                    result.FileName = actFileName;
                    result.State = 1;


                    if (isPartUpload)
                    {

                        var dirPath = Path.Combine(FileOperateInfo.FileBasePath, id);
                        DirectoryHelper.IsExistOrCreate(dirPath);

                        var filePartPath = Path.Combine(dirPath, file.FileName);
                        FileStream partStream = null;
                        try

                        {
                            partStream = new FileStream(filePartPath, FileMode.Create);
                            file.CopyTo(partStream);
                            partStream.Flush();
                        }
                        catch (Exception ex)
                        {
                            if (System.IO.File.Exists(filePartPath))
                            {
                                System.IO.File.Delete(filePartPath);
                            }
                            result.State = 3;
                            result.Msg = ex.Message;
                            return result;
                        }
                        finally
                        {
                            if (partStream != null)
                            {
                                partStream.Close();
                            }
                        }

                        FileStateAdd(fileState, file.FileName);

                        var tempFiles = DirectoryHelper.GetDirectFileCounts(dirPath);
                        var fileInfos = GetFilePartNames(id);
                        var index = fileInfos != null && fileInfos.Count() > 0 ? fileInfos.Length : 0;

                        if (enti.total != 0 && tempFiles.Count() == enti.total && index == enti.total)
                        {
                            FileMerge(fileState, enti, dirPath, actFileName);
                            result.State = 2;
                            fileState.FileStatus = "2";
                            _repository.Update(fileState);
                            _unitOfWork.Commit();
                            return result;
                        }
                        else
                        {
                            return result;
                        }
                    }
                    else
                    {
                        if (!fileTuple.Item2)
                        {
                            var fileSavePath = Path.Combine(FileOperateInfo.FileBasePath, id + fileState.FileType);
                            using (FileStream stream = new FileStream(fileSavePath, FileMode.Create))
                            {
                                file.CopyTo(stream);
                                stream.Flush();
                                stream.Close();
                            }

                            result.State = 2;
                            return result;
                        }
                        else
                        {
                            result.FileName = actFileName;
                            result.State = 2;
                            return result;
                        }
                    }
                }
                else
                {
                    return new FileResult() { Msg = "文件上传个数出错！" };
                }
            }
            catch (Exception ex)
            {
                FileResult result = new FileResult();
                result.Msg = ex.Message;
                result.State = 4;
                return result;
            }
        }

        private void FileMerge(FsFileinfoEntity state, ParamEnti enti, string dirPath, string actFileName)
        {
            var fileSavePath = Path.Combine(FileOperateInfo.FileBasePath, state.FileId + state.FileType);
            if (!System.IO.File.Exists(fileSavePath))
            {
                lock (objMergeLock)
                {
                    if (!System.IO.File.Exists(fileSavePath))
                    {
                        using (FileStream stream = new FileStream(fileSavePath, FileMode.Create))
                        {
                            stream.Position = 0;
                            for (int i = 1; i <= enti.total; i++)
                            {
                                var fileCurPartPath = Path.Combine(dirPath, actFileName + filePartName + i.ToString());
                                using (FileStream tempStream = new FileStream(fileCurPartPath, FileMode.Open, FileAccess.Read))
                                {

                                    var length = (int)tempStream.Length;
                                    byte[] bytes = new byte[length];
                                    tempStream.Read(bytes, 0, length);
                                    stream.Write(bytes, 0, length);
                                    //stream.Position += length;                                                 
                                    tempStream.Close();
                                }

                            }

                            stream.Flush();
                            stream.Close();

                            //删除临时文件
                            DirectoryHelper.DropDirtory(dirPath);
                        }
                    }
                }
            }
        }


        [HttpGet]
        public void Download(string fileId)
        {
            var fileEnti = GetFileInfoById(fileId);

            if (fileEnti.Item2)
            {
                var para = fileEnti.Item1;
                string fileType = para.FileType;
                var fileDownloadPath = Path.Combine(FileOperateInfo.FileBasePath, fileId + fileType);
                if (System.IO.File.Exists(fileDownloadPath))
                {
                    long chunkSize = 8 * 1024 * 1024;
                    byte[] buffer = new byte[chunkSize];

                    long dataToRead = 0;
                    FileStream stream = null;
                    try
                    {
                        //打开文件   
                        stream = new FileStream(fileDownloadPath, FileMode.Open, FileAccess.Read, FileShare.Read);

                        dataToRead = stream.Length;

                        //添加Http头   
                        Response.ContentType = "application/octet-stream";
                        Response.Headers.Add("Content-Disposition", "attachement;filename=" + HttpUtility.UrlEncode(para.FileName));
                        Response.Headers.Add("Content-Length", dataToRead.ToString());

                        while (dataToRead > 0)
                        {
                            if (!HttpContext.RequestAborted.IsCancellationRequested)
                            {
                                int length = stream.Read(buffer, 0, Convert.ToInt32(chunkSize));
                                Response.Body.Write(buffer, 0, length);
                                Response.Body.Flush();
                                dataToRead -= length;
                            }
                            else
                            {
                                //防止client失去连接   
                                dataToRead = -1;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Response.StatusCode = 500;
                    }
                    finally
                    {
                        if (stream != null)
                        {
                            stream.Close();
                        }
                        Response.Body.Close();
                    }

                }

            }

        }


        public void FileStateAdd(FsFileinfoEntity state, string partName)
        {
            fileRedis.SetCacheInfo(state.FileId, partName);
        }

        public string[] GetFilePartNames(string fileId)
        {
            return fileRedis.GetCacheInfo(fileId);
        }

        public (FsFileinfoEntity, bool) GetFileInfoById(string fileId)
        {
            var fileEnti = fileRedis.GetFileInfo<FsFileinfoEntity>(FileSystemRepository.FileIdRedisKey, fileId);
            bool isExit = true;
            if (fileEnti == null)
            {
                fileEnti = _repository.Get((p) => p.FileId == fileId);
                if (fileEnti == null)
                {
                    isExit = false;
                }
            }
            return (fileEnti, isExit);
        }

        public (FsFileinfoEntity, bool) GetMapFileId(ParamEnti enti, bool isInsert = true)
        {
            bool isExist = false;
            var code = $"{enti.fileName.Trim()}_{enti.fileType.Trim()}_{enti.lastModified}_{enti.totalSize}";
            var fileEnti = fileRedis.GetFileInfo<FsFileinfoEntity>(FileSystemRepository.FileRedisKey, code);
            if (fileEnti == null)
            {
                lock (objLock)
                {
                    fileEnti = fileRedis.GetFileInfo<FsFileinfoEntity>(FileSystemRepository.FileRedisKey, code);
                    if (fileEnti == null)
                    {

                        var items = _repository.GetList(p => p.FileName == enti.fileName && p.FileType == enti.fileType
                        && p.LastModify == enti.lastModified && p.FileSize == enti.totalSize);
                        if (items != null && items.Count() > 0)
                        {
                            isExist = true;
                            fileEnti = items.First();
                        }
                        else
                        {
                            if (isInsert)
                            {

                                fileEnti = new FsFileinfoEntity();
                                fileEnti.FileId = Guid.NewGuid().ToString();
                                fileEnti.FileChunkSize = enti.fileChunkSize;
                                fileEnti.CreateTime = DateTime.Now;
                                fileEnti.FileType = enti.fileType;
                                fileEnti.FileName = enti.fileName;
                                fileEnti.FileActualName = enti.fileName;
                                fileEnti.LastModify = enti.lastModified;
                                fileEnti.FileSize = enti.totalSize;
                                fileEnti.ModuleName = enti.ModuleName;
                                fileEnti.SubModuleName = enti.SubModuleName;
                                fileEnti.UploadDate = DateTime.Now;
                                fileEnti.Status = 1;
                                fileEnti.FileStatus = "1";
                                _repository.Insert(fileEnti);
                                _unitOfWork.Commit();
                                fileRedis.SetFileInfo<FsFileinfoEntity>(FileSystemRepository.FileRedisKey, code, fileEnti);
                                fileRedis.SetFileInfo<FsFileinfoEntity>(FileSystemRepository.FileIdRedisKey, fileEnti.FileId, fileEnti);
                            }
                        }


                    }
                    else
                    {
                        isExist = true;
                    }
                }

            }
            else
            {
                isExist = true;

            }

            return (fileEnti, isExist);
        }
    }
    public static class FileOperateInfo
    {
        public static string FileBasePath { get; set; }
        static FileOperateInfo()
        {
            string finalPath = "";
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                finalPath = Path.Combine("/opt", "SigerUploadManager");
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                var curAppPath = AppDomain.CurrentDomain.BaseDirectory;
                var root = curAppPath.Substring(0, curAppPath.IndexOf(":") + 1);
                //todo:获取配置
                finalPath = Path.Combine(root, "SigerUploadManager");

            }
            else
            {
                throw new Exception("不支持当前操作系统!");
            }
            DirectoryHelper.IsExistOrCreate(finalPath);
            FileBasePath = finalPath;
        }

        public static ParamEnti GetFileInfo(IFormFile actFile, ParamEnti param)
        {
            var fileType = param.fileName.Substring(param.fileName.LastIndexOf('.'));
            param.fileType = fileType;
            //param.fileName = actFile.FileName;
            //param.lastModified = 0;
            if (param.totalSize == 0)
            {
                param.totalSize = actFile.Length;
            }
            return param;
        }
    }
    public static class DirectoryHelper
    {
        public static bool IsExistOrCreate(string dirPath)
        {
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
                return false;
            }
            return true;
        }

        public static string[] GetDirectFileCounts(string dirPath)
        {
            if (!Directory.Exists(dirPath))
            {
                return new string[0];
            }
            var files = Directory.GetFiles(dirPath);
            return files;
        }

        public static bool DropDirtory(string dirPath)
        {
            if (Directory.Exists(dirPath))
            {
                Directory.Delete(dirPath, true);
                return true;
            }
            return false;
        }
    }

    public class FileState
    {
        public string FileId { get; set; }


    }

    public class FileResult
    {
        public string FileId { get; set; }

        public string FileName { get; set; }

        public double FileTotalSize { get; set; }

        public double FileChunkSize { get; set; }

        //0- 待处理,1-执行中,2-数据合并成功,3-失败,4-异常报错
        public int State { get; set; }

        public string Msg { get; set; }

        public bool IsHasUploaded { get; set; }

        public bool IsFileBreak { get; set; } = false;

        public BreakUploadInfo BreakInfo { get; set; }

    }

    public class BreakUploadInfo
    {
        public int[] NeedBreakItems { get; set; }
        public int[] HadUploadItems { get; set; }
    }

    public class ParamEnti
    {
        #region 上传文件必须包含的字段
        public int total { get; set; }

        public int curIdx { get; set; }

        public int startSize { get; set; }

        public int endSize { get; set; }
        #endregion
        #region 可选字段
        public string ModuleName { get; set; }

        public string SubModuleName { get; set; }
        #endregion

        public double totalSize { get; set; }
        public string fileName { get; set; }

        public string fileType { get; set; }

        public double lastModified { get; set; }

        public double fileChunkSize { get; set; }




        public string DisplayFileName { get; set; }

    }
}