namespace Hims.Api.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;

    using Domain.Helpers;
    using Domain.Services;

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;

    using Models.Appointment;

    using Npgsql;

    using Shared.DataFilters;
    using Shared.EntityModels;
    using Shared.UserModels.Filters;

    using Utilities;

    using Hims.Api.Models.Patient;
    using Hims.Shared.Library.Enums;

    /// <inheritdoc />
    [Authorize]
    [Route("api/appointment-transactions")]
    [Consumes("application/json")]
    [Produces("application/json")]
    public class AppointmentTransactionsController : BaseController
    {
        /// <summary>
        /// The appointment transaction services.
        /// </summary>
        private readonly IAppointmentTransactionService appointmentTransactionsServices;

        /// <summary>
        /// The AES helper.
        /// </summary>
        private readonly IAESHelper aesHelper;

        /// <summary>
        /// The account services.
        /// </summary>
        private readonly IAccountService accountServices;

        /// <summary>
        /// The amazon s3 helper.
        /// </summary>
        private readonly IDocumentHelper documentHelper;

        /// <inheritdoc />
        public AppointmentTransactionsController(IAppointmentTransactionService appointmentTransactionsServices, IAESHelper aesHelper, IAccountService accountServices, IDocumentHelper documentHelper,ITimelineService timelineService)
        {
            this.appointmentTransactionsServices = appointmentTransactionsServices;
            this.accountServices = accountServices;
            this.documentHelper = documentHelper;
            this.aesHelper = aesHelper;
        }

        /// <summary>
        /// The fetch appointment transactions.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// The list of appointments.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of appointment transactions.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetch")]
        [ProducesResponseType(typeof(List<AppointmentTransactionModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var appointmentTransactions = await this.appointmentTransactionsServices.FetchAsync(model);
            if (appointmentTransactions == null)
            {
                return this.ServerError();
            }

            foreach (var item in appointmentTransactions)
            {
                item.EncryptedAppointmentId = this.aesHelper.Encode(item.AppointmentId.ToString());
            }

            return this.Success(appointmentTransactions);
        }

        /// <summary>
        /// The fetch provider transactions async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HttpPost]
        [Route("fetch-provider-transactions")]
        [ProducesResponseType(typeof(List<AppointmentTransactionModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchProviderTransactionsAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var appointmentTransactions = await this.appointmentTransactionsServices.FetchProviderTransactionsAsync(model);
            if (appointmentTransactions == null)
            {
                return this.ServerError();
            }

            foreach (var item in appointmentTransactions)
            {
                item.EncryptedAppointmentId = this.aesHelper.Encode(item.AppointmentId.ToString());
                item.AppointmentTimeString = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd"))
                    .Add(item.AppointmentTime).ToString("hh:mm tt");
            }

            return this.Success(appointmentTransactions);
        }

        /// <summary>
        /// The fetch appointment transactions.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// The list of appointments.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of appointment transactions.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetchRefund")]
        [ProducesResponseType(typeof(List<RefundModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchRefundAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var refundTransactions = await this.appointmentTransactionsServices.FetchRefundAsync(model);
            return refundTransactions == null ? this.ServerError() : this.Success(refundTransactions);
        }

        /// <summary>
        /// The fetch appointment transactions.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// The list of appointments.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of appointment transactions.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetchPayout")]
        [ProducesResponseType(typeof(List<RefundModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchPayoutAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var payoutDetails = await this.appointmentTransactionsServices.FetchPayoutAsync(model);
            return payoutDetails == null ? this.ServerError() : this.Success(payoutDetails);
        }

        /// <summary>
        /// The fetch appointment transactions.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// The list of appointments.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of appointment transactions.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetchPayoutDetails")]
        [ProducesResponseType(typeof(List<AppointmentTransactionModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchPayoutDetailsAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var payoutDetails = await this.appointmentTransactionsServices.FetchPayoutDetailsAsync(model);
            return payoutDetails == null ? this.ServerError() : this.Success(payoutDetails);
        }

        /// <summary>
        /// The find appointment transaction details.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// Appointment transaction details.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Appointment transaction details.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("find")]
        [ProducesResponseType(typeof(List<AppointmentTransactionModel>), 200)]
        [ProducesResponseType(typeof(string), 400)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FindTransactionDetailsAsync([FromBody] FindAppointmentTransactionRequest model)
        {
            model = (FindAppointmentTransactionRequest)EmptyFilter.Handler(model);
            var transactionDetails = await this.appointmentTransactionsServices.FindTransactionDetailsAsync(model.AppointmentTransactionId);
            return string.IsNullOrEmpty(transactionDetails) ? this.BadRequest("Sorry! We don't have a transaction details in the system.") : this.Success(transactionDetails);
        }

        /// <summary>
        /// The find appointment transaction details.
        /// </summary>
        /// <param name="model">
        /// The appointment transaction filter model.
        /// </param>
        /// <returns>
        /// Appointment transaction details.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Appointment transaction details.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("settlement")]
        [ProducesResponseType(typeof(List<SettlementFilterModel>), 200)]
        [ProducesResponseType(typeof(string), 400)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FindSettlementDetailsAsync([FromBody] SettlementFilterModel model)
        {
            model = (SettlementFilterModel)EmptyFilter.Handler(model);
            var transactionDetails = await this.appointmentTransactionsServices.FindSettlementDetailsAsync(model);

            return transactionDetails == null ? this.ServerError() : this.Success(transactionDetails);
        }

        /// <summary>
        /// The find settlement details async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HttpPost]
        [Route("fetchAppointments")]
        [ProducesResponseType(typeof(List<AppointmentTransactionModel>), 200)]
        [ProducesResponseType(typeof(string), 400)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchAppointmentsDetailsAsync([FromBody] AppointmentTransactionFilterModel model)
        {
            model = (AppointmentTransactionFilterModel)EmptyFilter.Handler(model);
            var transactionDetails = await this.appointmentTransactionsServices.FetchAppointmentsDetailsAsync(model);
            if (transactionDetails == null)
            {
                return this.ServerError();
            }

            foreach (var item in transactionDetails)
            {
                item.EncryptedAppointmentId = this.aesHelper.Encode(item.AppointmentId.ToString());
            }

            return this.Success(transactionDetails);
        }

        /// <summary>
        /// The upload async.
        /// </summary>
        /// <param name="request">
        /// The request.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HttpPost]
        [Route("upload-patient-document")]
        [Consumes("multipart/form-data")]
        [Produces("application/json")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(400)]
        [ProducesResponseType(417)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> UploadAsync([FromForm] UploadPaymentDocumentRequest request)
        {
            request = (UploadPaymentDocumentRequest)EmptyFilter.Handler(request);

            var files = this.Request.Form.Files;
            if (files == null || files.Count == 0)
            {
                return this.BadRequest("We're sorry. There are no files to upload.");
            }

            if (files.Count > 10)
            {
                return this.BadRequest("Maximum of 12 files can be allowed to upload.");
            }

            var guid = await this.accountServices.FindGuidAsync(request.ProviderId, Roles.Provider);
            if (guid == Guid.Empty)
            {
                return this.BadRequest("Sorry! We don't have a patient in the system.");
            }

            var patientDocuments = new List<PaymentDocumentModel>();
            var index = 0;
            foreach (var file in files)
            {
                var model = new PaymentDocumentModel()
                {
                    DocumentType = request.DocumentType,
                    Description = request.Description,
                    DocumentName = index == 0 ? request.DocumentName : $"{request.DocumentName}_{index}",
                    UploadedBy = request.UploadedBy,
                    ProviderId = request.ProviderId,
                    ContentType = file.ContentType,
                    Size = file.Length,
                    UploadedDate = DateTime.UtcNow,
                    Year = request.Year,
                    Month = request.Month
                };

                var fileName = $"{model.DocumentName}_{DateTime.UtcNow.Ticks}{Path.GetExtension(file.FileName)}";
                model.DocumentUrl = await this.documentHelper.UploadDocumentAsync(file, guid, model.DocumentType, fileName);
                model.ThumbnailUrl = this.documentHelper.GetThumbnail(file.ContentType);

                patientDocuments.Add(model);
                index++;
            }

            var response = await this.appointmentTransactionsServices.AddAsync(patientDocuments);
            var enumerable = response.ToList();
            if (!enumerable.Any())
            {
                return this.ServerError();
            }

            return this.Success($"Document{(files.Count > 0 ? "s" : string.Empty)} has been uploaded successfully.");
        }

        /// <summary>
        /// The modify status of patient document.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - PatientDocument status updated successfully.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPut]
        [Route("update-payment-document")]
        [Consumes("application/json")]
        [Produces("application/json")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> UpdateAsync([FromBody] PaymentDocumentModel model)
        {
            var document = await this.appointmentTransactionsServices.GetAsync(model.PaymentDocumentId);

            model = (PaymentDocumentModel)EmptyFilter.Handler(model);
            var response = await this.appointmentTransactionsServices.UpdateAsync(model);
            if (response == 0)
            {
                return this.ServerError();
            }

            return this.Success($" Document({document.DocumentName}, {document.DocumentType}) details has been updated successfully.");
        }

        /// <summary>
        /// The fetch async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HttpPost]
        [AllowAnonymous]
        [Route("fetch-payment-documents")]
        [Consumes("application/json")]
        [Produces("application/json")]
        [ProducesResponseType(typeof(List<PatientDocumentModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchAsync([FromBody] PaymentDocumentFilterModel model)
        {
            model = (PaymentDocumentFilterModel)EmptyFilter.Handler(model);

            if (!string.IsNullOrEmpty(model.EncryptedProviderId))
            {
                model.ProviderId = Convert.ToInt32(this.aesHelper.Decode(model.EncryptedProviderId));
            }

            var patientDocuments = await this.appointmentTransactionsServices.FetchPaymentAsync(model);

            if (!string.IsNullOrEmpty(model.EncryptedProviderId) && (patientDocuments == null || !patientDocuments.Any()))
            {
                patientDocuments = new List<PaymentDocumentModel> { new PaymentDocumentModel { ProviderId = Convert.ToInt32(model.ProviderId) } };
                return this.Success(patientDocuments);
            }

            if (patientDocuments == null)
            {
                return this.ServerError();
            }

            return this.Success(patientDocuments);
        }

        /// <summary>
        /// The delete async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HttpPost]
        [Route("delete")]
        [Consumes("application/json")]
        [Produces("application/json")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(409)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> DeleteAsync([FromBody] PaymentDocumentModel model)
        {
            try
            {
                //var document = await this.appointmentTransactionsServices.GetAsync(model.PatientDocumentId);
                model = (PaymentDocumentModel)EmptyFilter.Handler(model);
                var response = await this.appointmentTransactionsServices.DeleteAsync(model);
                if (response == 0)
                {
                    return this.ServerError();
                }
                // await this.documentHelper.DeleteAsync(response.Guid, response.DocumentUrl);
              
                return this.Success($"Document  has been deleted successfully.");
            }
            catch (NpgsqlException exception)
            {
                if (exception.Message.Contains("violates foreign key constraint"))
                {
                    return this.Conflict("Patient document can't be deleted. Please contact administrator.");
                }

                return this.ServerError();
            }
        }
    }
}