using System;
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.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using AyaNova.Models;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Biz;
using AyaNova.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace AyaNova.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>
        /// Duplicate Report
        /// (Wiki and Attachments are not duplicated)
        /// </summary>
        /// <param name="id">Source object id</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns>Report</returns>
        [HttpPost("duplicate/{id}")]
        public async Task<IActionResult> DuplicateReport([FromRoute] long id, 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.DuplicateAsync(id);
            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>
        /// Put (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);//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>
        /// 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="ayType">Type of object</param>
        /// <returns>Name / id report list of allowed reports for role of requester</returns>
        [HttpGet("list/{ayType}")]
        public async Task<IActionResult> GetReportList([FromRoute] AyaType ayType)
        {
            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, ayType))
                return StatusCode(403, new ApiNotAuthorizedResponse());
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            var o = await biz.GetReportListAsync(ayType);
            if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
            return Ok(ApiOkResponse.Response(o));
        }


        /// <summary>
        /// Get data from id list in format used by report designer
        /// </summary>
        /// <param name="reportDataParam">Data required for report</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns></returns>
        [HttpPost("data")]
        public async Task<IActionResult> GetReportData([FromBody] ReportDataParameter reportDataParam, 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));
            var reportData = await biz.GetReportData(reportDataParam);
            if (reportData == null)
                return BadRequest(new ApiErrorResponse(biz.Errors));
            else
                return Ok(ApiOkResponse.Response(reportData));
        }


        /// <summary>
        /// Render Report
        /// </summary>
        /// <param name="reportParam">report id and object id values for object type specified in report template</param>
        /// <param name="apiVersion">From route path</param>
        /// <returns>downloadable pdf name</returns>
        [HttpPost("render")]
        public async Task<IActionResult> RenderReport([FromBody] RenderReportParameter reportParam, 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/v8/";
            try
            {
                var result = await biz.RenderReport(reportParam, API_URL);

                if (string.IsNullOrWhiteSpace(result))
                    return BadRequest(new ApiErrorResponse(biz.Errors));
                else
                    return Ok(ApiOkResponse.Response(result));
            }
            catch (System.Exception ex)
            {
                //The Javascript evaluation stack trace can be in the message making it long and internalized,
                //however the info is useful as it can indicate exactly which function failed etc so sending it all back is best
                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
            }
        }


        /// <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 AyaNova.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(15000000)]//currently the largest v7 export for a report template is 828kb, I'm guessing 15mb is more than enough
        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);



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





    }//eoc
}//eons