using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Sockeye.Models;
using Sockeye.Api.ControllerHelpers;
using Sockeye.Biz;
using Sockeye.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Sockeye.Api.Controllers
{
    [ApiController]
    [ApiVersion("8.0")]
    [Route("api/v{version:apiVersion}/report")]
    [Produces("application/json")]
    [Authorize]
    public class ReportController : ControllerBase
    {
        private readonly AyContext ct;
        private readonly ILogger<ReportController> log;
        private readonly ApiServerState serverState;

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


        /// <summary>
        /// Create Report
        /// </summary>
        /// <param name="newObject"></param>
        /// <param name="apiVersion">From route path</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> PostReport([FromBody] Report newObject, ApiVersion apiVersion)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            Report o = await biz.CreateAsync(newObject);
            if (o == null)
                return BadRequest(new ApiErrorResponse(biz.Errors));
            else
                return CreatedAtAction(nameof(ReportController.GetReport), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
        }


        /// <summary>
        /// Get Report
        /// </summary>
        /// <param name="id"></param>
        /// <returns>Report</returns>
        [HttpGet("{id}")]
        public async Task<IActionResult> GetReport([FromRoute] long id)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            var o = await biz.GetAsync(id);
            if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            return Ok(ApiOkResponse.Response(o));
        }

        /// <summary>
        /// Update Report
        /// </summary>        
        /// <param name="updatedObject"></param>
        /// <returns></returns>
        [HttpPut]
        public async Task<IActionResult> PutReport([FromBody] Report updatedObject)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            ReportBiz biz = ReportBiz.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>
        /// Delete Report
        /// </summary>
        /// <param name="id"></param>
        /// <returns>NoContent</returns>
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteReport([FromRoute] long id)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!await biz.DeleteAsync(id))
                return BadRequest(new ApiErrorResponse(biz.Errors));
            return NoContent();
        }



        /// <summary>
        /// Get Report list for object
        /// </summary>
        /// <param name="aType">Type of object</param>
        /// <returns>Name / id report list of allowed reports for role of requester</returns>
        [HttpGet("list/{aType}")]
        public async Task<IActionResult> GetReportList([FromRoute] SockType aType)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            //extra check if they have rights to the type of object in question, this nips it in the bud before they even get to the fetch data stage later
            if (!Authorized.HasReadFullRole(HttpContext.Items, aType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            var o = await biz.GetReportListAsync(aType);
            if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            return Ok(ApiOkResponse.Response(o));
        }


        /// <summary>
        /// Get a limited amount of sample data from id list in format used by report designer
        /// </summary>
        /// <param name="selectedRequest">Data required for report</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns></returns>
        [HttpPost("data")]
        public async Task<IActionResult> GetReportData([FromBody] DataListSelectedRequest selectedRequest, ApiVersion apiVersion)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));

            //cap the data returned
            selectedRequest.ReportDesignerSample = true;
            JArray reportData;
            try
            {
                reportData = await biz.GetReportDataForReportDesigner(selectedRequest);
                if (reportData == null)
                    return BadRequest(new ApiErrorResponse(biz.Errors));
                else
                    return Ok(ApiOkResponse.Response(reportData));
            }
            catch (ReportRenderTimeOutException)
            {
                log.LogInformation($"GetReportData timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} ");
                //note: this route is called by the report designer to get a limited subset of records so we should never see this error but including it for completeness
                //report designer should show this as a general error  
                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records"));

            }
        }


        /// <summary>
        /// Start Render Report job
        /// </summary>
        /// <param name="reportRequest">report id and object id values for object type specified in report template</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns>Job Id</returns>
        [HttpPost("render-job")]
        public async Task<IActionResult> RequestRenderReport([FromBody] DataListReportRequest reportRequest, ApiVersion apiVersion)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            var httpConnectionFeature = HttpContext.Features.Get<IHttpConnectionFeature>();
            var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/{SockeyeVersion.CurrentApiVersion}/";
            var result = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT), API_URL, UserNameFromContext.Name(HttpContext.Items));
            if (result == null)
                return BadRequest(new ApiErrorResponse(biz.Errors));
            else
                return Accepted(new { JobId = result });
        }

        /// <summary>
        /// Attempt cancel render job
        /// </summary>       
        /// <param name="gid"></param>
        /// <returns>nothing</returns>
        [HttpPost("request-cancel/{gid}")]
        public async Task<IActionResult> RequestCancelJob([FromRoute] Guid gid)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
            if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            log.LogDebug($"request-cancel called for report rendering job id {gid}");
            await biz.CancelJob(gid);
            return NoContent();
        }


        /// <summary>
        /// Download a rendered report
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="t">download token</param>
        /// <returns></returns>
        [HttpGet("download/{fileName}")]
        [AllowAnonymous]
        public async Task<IActionResult> DownloadAsync([FromRoute] string fileName, [FromQuery] string t)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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

            if (!FileUtil.TemporaryFileExists(fileName))
            {
                await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
                return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            }

            var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName);
            return PhysicalFile(FilePath, "application/pdf");
        }



        /// <summary>
        /// Download report template
        /// </summary>
        /// <param name="id">Report id</param>
        /// <param name="t">download token</param>
        /// <returns>A single report template as a file</returns>
        [AllowAnonymous]
        [HttpGet("export/{id}")]
        public async Task<IActionResult> DownloadTemplate([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(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
                return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
            }

            var o = await ct.Report.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" }) });
            var bytes = System.Text.Encoding.UTF8.GetBytes(asText);
            var file = new FileContentResult(bytes, "application/octet-stream");
            file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".ayrt";
            return file;
        }




        /// <summary>
        /// Upload Reprot template export file
        /// Max 15mb total
        /// </summary>
        /// <returns>Accepted</returns>
        [Authorize]
        [HttpPost("upload")]
        [DisableFormValueModelBinding]
        [RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_REPORT_TEMPLATE_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}"));

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

                //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))
                {

                    //delete temp files
                    ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
                    //file too large is most likely issue so in that case return this localized properly
                    if (uploadFormData.Error.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_REPORT_TEMPLATE_UPLOAD_BYTES))));
                    }
                    else//not too big, something else                    
                        return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, uploadFormData.Error));
                }

                List<UploadFileData> FileData = new List<UploadFileData>();

                if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
                {
                    return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData"));
                }

                //fileData in JSON stringify format
                FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());

                //Instantiate the business object handler
                ReportBiz biz = ReportBiz.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 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);
        //         }
        //     }
        // }
        //-----------------------------------------





    }//eoc
}//eons