using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

using AyaNova.Models;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Biz;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;



namespace AyaNova.Api.Controllers
{
    //DOCUMENTATING THE API
    //https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments
    //https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments

    /// <summary>
    /// Translation controller
    /// </summary>
    [ApiController]
    [ApiVersion("8.0")]
    [Route("api/v{version:apiVersion}/translation")]
    [Produces("application/json")]
    [Authorize]
    public class TranslationController : ControllerBase
    {
        private readonly AyContext ct;
        private readonly ILogger<TranslationController> log;
        private readonly ApiServerState serverState;


        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="dbcontext"></param>
        /// <param name="logger"></param>
        /// <param name="apiServerState"></param>  
        public TranslationController(AyContext dbcontext, ILogger<TranslationController> logger, ApiServerState apiServerState)
        {
            ct = dbcontext;
            log = logger;
            serverState = apiServerState;
        }




        /// <summary>
        /// Get Translation all values
        /// </summary>
        /// <param name="id"></param>
        /// <returns>A single Translation and it's values</returns>
        [HttpGet("{id}")]
        public async Task<IActionResult> GetTranslation([FromRoute] long id)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));


            if (!ModelState.IsValid)
            {
                return BadRequest(new ApiErrorResponse(ModelState));
            }

            //Instantiate the business object handler        
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);

            var o = await biz.GetAsync(id);

            if (o == null)
            {
                return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            }

            return Ok(ApiOkResponse.Response(o));
        }



        /// <summary>
        /// Put (update) Translation
        /// 
        /// </summary>        
        /// <param name="updatedObject"></param>
        /// <returns></returns>
        [HttpPut]
        public async Task<IActionResult> PutTranslation([FromBody] Translation updatedObject)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            var o = await biz.PutAsync(updatedObject);//In future may need to return entire object, for now just concurrency token 
            if (o == null)
            {
                if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
                    return StatusCode(409, new ApiErrorResponse(biz.Errors));
                else
                    return BadRequest(new ApiErrorResponse(biz.Errors));
            }
            return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
        }



        /// <summary>
        /// Get Translations list
        /// </summary>
        /// <returns>List in alphabetical order of all Translations</returns>    
        [HttpGet("list")]
        public async Task<IActionResult> TranslationList()
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

            //Instantiate the business object handler
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);

            var l = await biz.GetTranslationListAsync();
            return Ok(ApiOkResponse.Response(l));
        }


#if (DEBUG)
        /// <summary>
        /// Get a coverage report of translation keys used versus unused
        /// </summary>
        /// <returns>Report of all unique translation keys requested since last  server reboot</returns>    
        [HttpGet("translationkeycoverage")]
        public async Task<IActionResult> TranslationKeyCoverage()
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

            //Instantiate the business object handler
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);

            var l = await biz.TranslationKeyCoverageAsync();
            return Ok(ApiOkResponse.Response(l));
        }
#endif


        /// <summary>
        /// Get subset of translation values
        /// </summary>
        /// <param name="inObj">List of translation key strings</param>
        /// <returns>A key value array of translation text values</returns>    
        [HttpPost("subset")]
        public async Task<IActionResult> SubSet([FromBody] List<string> inObj)
        {
            if (serverState.IsClosed)
            {
                //Exception for SuperUser account to handle licensing issues
                if (UserIdFromContext.Id(HttpContext.Items) != 1)
                    return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            }

            //Instantiate the business object handler

            //Instantiate the business object handler
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);

            var l = await biz.GetSubsetAsync(inObj);
            return Ok(ApiOkResponse.Response(l));
        }



        /// <summary>
        /// Duplicate       
        /// </summary>
        /// <param name="id">Source object id</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns>Duplicate</returns>
        [HttpPost("duplicate/{id}")]
        public async Task<IActionResult> DuplicateTranslation([FromRoute] long id, ApiVersion apiVersion)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            Translation o = await biz.DuplicateAsync(id);
            if (o == null)
                return BadRequest(new ApiErrorResponse(biz.Errors));
            else
                return CreatedAtAction(nameof(TranslationController.GetTranslation), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
        }



        /// <summary>
        /// Delete Translation
        /// </summary>
        /// <param name="id"></param>
        /// <returns>Ok</returns>
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTranslation([FromRoute] long id)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

            if (!ModelState.IsValid)
            {
                return BadRequest(new ApiErrorResponse(ModelState));
            }


            //Fetch translation and it's children
            //(fetch here so can return proper REST responses on failing basic validity)
            var dbObject = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id);
            if (dbObject == null)
            {
                return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            }

            if (!Authorized.HasDeleteRole(HttpContext.Items, AyaType.Translation))
            {
                return StatusCode(403, new ApiNotAuthorizedResponse());
            }


            //Instantiate the business object handler
            TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
            if (!await biz.DeleteAsync(dbObject))
            {
                return BadRequest(new ApiErrorResponse(biz.Errors));
            }

            return NoContent();
        }








        /// <summary>
        /// Get Translation all values
        /// </summary>
        /// <param name="id"></param>
        /// <param name="t">download token</param>
        /// <returns>A single Translation and it's values</returns>
        [AllowAnonymous]
        [HttpGet("download/{id}")]
        public async Task<IActionResult> DownloadTranslation([FromRoute] long id, [FromQuery] string t)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));

            if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
            {
                await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
                return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
            }

            var o = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id);
            //turn into correct format and then send as file
            if (o == null)
            {
                return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            }
            var asText = Newtonsoft.Json.JsonConvert.SerializeObject(
                o,
                Newtonsoft.Json.Formatting.None,
                new JsonSerializerSettings { ContractResolver = new AyaNova.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "TranslationId" }) });
            var bytes = System.Text.Encoding.UTF8.GetBytes(asText);
            var file = new FileContentResult(bytes, "application/octet-stream");
            file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".json";
            return file;
        }




        /// <summary>
        /// Upload Translation export file
        /// Max 15mb total
        /// </summary>
        /// <returns>Accepted</returns>
        [Authorize]
        [HttpPost("upload")]
        [DisableFormValueModelBinding]
        [RequestSizeLimit(15000000)]//currently export file is 200kb * 50 maximum at a time = 15mb  https://github.com/aspnet/Announcements/issues/267
        public async Task<IActionResult> UploadAsync()
        {
            //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming

            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));


            // AyaTypeId attachToObject = null;
            ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null;
            try
            {
                if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
                    return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));

                //Save uploads to disk under temporary file names until we decide how to handle them
                uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);

                bool badRequest = false;
                string UploadObjectType = string.Empty;
                string UploadObjectId = string.Empty;
                string errorMessage = string.Empty;
                string Notes = string.Empty;
                List<UploadFileData> FileData = new List<UploadFileData>();

                if (
                !uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
                {
                    badRequest = true;
                    errorMessage = "Missing required FormFieldData value: FileData";
                }
                if (!badRequest)
                {
                    if (uploadFormData.FormFieldData.ContainsKey("ObjectType"))
                        UploadObjectType = uploadFormData.FormFieldData["ObjectType"].ToString();
                    if (uploadFormData.FormFieldData.ContainsKey("ObjectId"))
                        UploadObjectId = uploadFormData.FormFieldData["ObjectId"].ToString();
                    if (uploadFormData.FormFieldData.ContainsKey("Notes"))
                        Notes = uploadFormData.FormFieldData["Notes"].ToString();
                    //fileData in JSON stringify format which contains the actual last modified dates etc
                    //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]"
                    FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());

                }


                //  long UserId = UserIdFromContext.Id(HttpContext.Items);
                //Instantiate the business object handler
                TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);


                //We have our files now can parse and insert into db
                if (uploadFormData.UploadedFiles.Count > 0)
                {
                    //deserialize each file and import
                    foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
                    {
                        JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName));
                        if (!await biz.ImportAsync(o))
                        {
                            //delete all the files temporarily uploaded and return bad request
                            DeleteTempUploadFile(uploadFormData);
                            return BadRequest(new ApiErrorResponse(biz.Errors));
                        }
                    }
                }
            }
            catch (System.IO.InvalidDataException ex)
            {
                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
            }
            finally
            {
                //delete all the files temporarily uploaded and return bad request

                DeleteTempUploadFile(uploadFormData);
            }

            //Return the list of attachment ids and filenames
            return Accepted();
        }


        private static void DeleteTempUploadFile(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData)
        {
            if (uploadFormData.UploadedFiles.Count > 0)
            {
                foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
                {
                    System.IO.File.Delete(a.InitialUploadedPathName);
                }
            }
        }


#if (DEBUG)
        public class TranslationCoverageInfo
        {
            public List<string> RequestedKeys { get; set; }
            public int RequestedKeyCount { get; set; }
            public List<string> NotRequestedKeys { get; set; }
            public int NotRequestedKeyCount { get; set; }

            public TranslationCoverageInfo()
            {
                RequestedKeys = new List<string>();
                NotRequestedKeys = new List<string>();
            }

        }
#endif

    }
}