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 Sockeye.Models;
using Sockeye.Api.ControllerHelpers;
using Sockeye.Biz;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;



namespace Sockeye.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>
        /// 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); 
            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>
        /// Get subset of translation values for specific translation Id
        /// </summary>
        /// <param name="id"></param>
        /// <param name="inObj">List of translation key strings</param>
        /// <returns>A key value array of translation text values</returns>    
        [HttpPost("subset/{id}")]
        [AllowAnonymous]
        public async Task<IActionResult> SubSet([FromRoute] long id, [FromBody] List<string> inObj)
        {
            //## NOTE: This route is ONLY used at present for the reset password form at the client
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            var l = await TranslationBiz.GetSpecifiedTranslationSubsetStaticAsync(inObj, id);
            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, SockType.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(Sockeye.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 Sockeye.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(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES)]
        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));


            // SockTypeId 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}"));


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

                //Save uploads to disk under temporary file names until we decide how to handle them
                uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
                if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
                {
                    errorMessage = uploadFormData.Error;
                    //delete temp files
                    ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
                    //file too large is most likely issue so in that case return this localized properly
                    if (errorMessage.Contains("413"))
                    {
                        var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
                        return BadRequest(new ApiErrorResponse(
                            ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
                            null,
                            String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES))));
                    }
                    else//not too big, something else                    
                        return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage));
                }


                if (
                !uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
                {
                    badRequest = true;
                    errorMessage = "Missing required FormFieldData value: FileData";
                }
                if (!badRequest)
                {
                    if (uploadFormData.FormFieldData.ContainsKey("SockType"))
                        UploadAType = uploadFormData.FormFieldData["SockType"].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
                            ApiUploadProcessor.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

                ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
            }

            //Return nothing
            return Accepted();
        }


        // /// <summary>
        // /// Put (UpdateTranslationItemDisplayText) 
        // /// Update a single key with new display text
        // /// </summary>     
        // /// <param name="inObj">NewText/Id/Concurrency token object. NewText is new display text, Id is TranslationItem Id, concurrency token is required</param>
        // /// <returns></returns>
        // [HttpPut("updatetranslationitemdisplaytext")]
        // public async Task<IActionResult> PutTranslationItemDisplayText([FromBody] NewTextIdConcurrencyTokenItem inObj)
        // {
        //     if (serverState.IsClosed)
        //         return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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

        //     var oFromDb = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == inObj.Id);

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

        //     //Now fetch translation for rights and to ensure not stock
        //     var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
        //     if (oDbParent == null)
        //     {
        //         return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
        //     }

        //     if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Translation))
        //     {
        //         return StatusCode(403, new ApiNotAuthorizedResponse());
        //     }

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

        //     try
        //     {
        //         if (!await biz.PutTranslationItemDisplayTextAsync(oFromDb, inObj, oDbParent))
        //         {
        //             return BadRequest(new ApiErrorResponse(biz.Errors));
        //         }
        //     }
        //     catch (DbUpdateConcurrencyException)
        //     {
        //         if (!await biz.TranslationItemExistsAsync(inObj.Id))
        //         {
        //             return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
        //         }
        //         else
        //         {
        //             //exists but was changed by another user
        //             //I considered returning new and old record, but where would it end?
        //             //Better to let the client decide what to do than to send extra data that is not required
        //             return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
        //         }
        //     }


        //     return Ok(ApiOkResponse.Response(new { Concurrency = oFromDb.Concurrency }));
        // }



        /// <summary>
        /// Put (UpdateTranslationItemDisplayText) 
        /// Update a list of items with new display text
        /// </summary>     
        /// <param name="inObj">Array of NewText/Id/Concurrency token objects. NewText is new display text, Id is TranslationItem Id, concurrency token is required</param>
        /// <returns></returns>
        [HttpPut("updatetranslationitemsdisplaytext")]
        public async Task<IActionResult> PutTranslationItemsDisplayText([FromBody] List<NewTextIdConcurrencyTokenItem> inObj)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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

            var oFromDb = await ct.TranslationItem.AsNoTracking().SingleOrDefaultAsync(z => z.Id == inObj[0].Id);

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

            //Now fetch translation for rights and to ensure not stock
            var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
            if (oDbParent == null)
            {
                return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            }

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

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

            try
            {
                if (!await biz.PutTranslationItemsDisplayTextAsync(inObj, oDbParent))
                {
                    return BadRequest(new ApiErrorResponse(biz.Errors));
                }
            }
            catch (DbUpdateConcurrencyException)
            {

                //exists but was changed by another user
                //I considered returning new and old record, but where would it end?
                //Better to let the client decide what to do than to send extra data that is not required
                return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
            }
            return NoContent();
        }




#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

    }
}