namespace Hims.Api.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Domain.Services;
    using Hims.Api.Models;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.CodeAnalysis.CSharp;
    using Npgsql;
    using Shared.DataFilters;
    using Shared.EntityModels;
    using Shared.Library.Enums;
    using Shared.UserModels.Filters;
    using Utilities;

    /// <inheritdoc />
    [Authorize]
    [Route("api/coupons")]
    [Consumes("application/json")]
    [Produces("application/json")]
    public class CouponsController : BaseController
    {
        /// <summary>
        /// The coupon services.
        /// </summary>
        private readonly ICouponService couponsServices;

        /// <summary>
        /// The audit log services.
        /// </summary>
        private readonly IAuditLogService auditLogServices;

        /// <inheritdoc />
        public CouponsController(ICouponService couponsServices, IAuditLogService auditLogServices)
        {
            this.couponsServices = couponsServices;
            this.auditLogServices = auditLogServices;
        }

        /// <summary>
        /// The fetch coupons.
        /// </summary>
        /// <param name="model">
        /// The coupon filter model.
        /// </param>
        /// <returns>
        /// The list of coupons.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of coupons.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetch")]
        [ProducesResponseType(typeof(List<CouponModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchAsync([FromBody]CouponFilterModel model)
        {
            model = (CouponFilterModel)EmptyFilter.Handler(model);
            var coupons = await this.couponsServices.FetchAsync(model);
            return coupons == null ? this.ServerError() : this.Success(coupons);
        }

        /// <summary>
        /// The fetch eligible coupons.
        /// </summary>
        /// <param name="model">
        /// The coupon filter model.
        /// </param>
        /// <returns>
        /// The list of eligible coupons for patient.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - List of coupons.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("fetch-eligible")]
        [ProducesResponseType(typeof(List<CouponModel>), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> FetchEligibleCouponsAsync([FromBody]CouponFilterModel model)
        {
            model = (CouponFilterModel)EmptyFilter.Handler(model);
            var coupons = await this.couponsServices.FetchEligibleCouponsAsync(model);
            return this.Success(coupons != null ? coupons : new List<CouponModel>());
        }

        /// <summary>
        /// The add coupon.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Coupon added successfully.
        /// - 409 - Coupon already exist.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("add")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(409)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> AddAsync([FromBody]CouponModel model,[FromHeader] LocationHeader header)
        {
            model = (CouponModel)EmptyFilter.Handler(model);
            var response = await this.couponsServices.AddAsync(model);
            switch (response)
            {
                case -1:
                    return this.Conflict("Given coupon code has already been exists with us.");
                case 0:
                    return this.ServerError();
            }

            var auditLogModel = new AuditLogModel
            {
                AccountId = model.CreatedBy,
                LogTypeId = (int)LogTypes.Coupons,
                LogFrom = (int)AccountType.Administrator,
                LogDate = DateTime.UtcNow,
                LogDescription = $"{model.CouponCode} coupon has been added.",
                LocationId = Convert.ToInt32(header.LocationId)
            };
            await this.auditLogServices.LogAsync(auditLogModel);

            return this.Success("Coupon has been added successfully.");
        }

        /// <summary>
        /// The update coupon.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Coupon updated successfully.
        /// - 409 - Coupon already exist.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPut]
        [Route("update")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(409)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> UpdateAsync([FromBody]CouponModel model,[FromHeader]LocationHeader header)
        {
            model = (CouponModel)EmptyFilter.Handler(model);
            var response = await this.couponsServices.UpdateAsync(model);
            switch (response)
            {
                case -1:
                    return this.Conflict("Given coupon code has already been exists with us.");
                case 0:
                    return this.ServerError();
            }

            var auditLogModel = new AuditLogModel
            {
                AccountId = model.ModifiedBy,
                LogTypeId = (int)LogTypes.Coupons,
                LogFrom = (int)AccountType.Administrator,
                LogDate = DateTime.UtcNow,
                LogDescription = $"{model.CouponCode} coupon has been updated.",
                LocationId = Convert.ToInt32(header.LocationId)
            };
            await this.auditLogServices.LogAsync(auditLogModel);

            return this.Success("Coupon has been updated successfully.");
        }

        /// <summary>
        /// The delete coupon.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Coupon deleted successfully.
        /// - 409 - Coupon can not be deleted.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Route("delete")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(409)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> DeleteAsync([FromBody]CouponModel model,[FromHeader] LocationHeader header)
        {
            try
            {
                model = (CouponModel)EmptyFilter.Handler(model);
                var response = await this.couponsServices.DeleteAsync(model.CouponId);
                if (response == 0)
                {
                    return this.ServerError();
                }

                var auditLogModel = new AuditLogModel
                {
                    AccountId = model.ModifiedBy,
                    LogTypeId = (int)LogTypes.Coupons,
                    LogFrom = (int)AccountType.Administrator,
                    LogDate = DateTime.UtcNow,
                    LogDescription = $"{model.CouponCode} coupon has been deleted.",
                    LocationId = Convert.ToInt32(header.LocationId)
                };
                await this.auditLogServices.LogAsync(auditLogModel);

                return this.Success("Coupon has been deleted successfully.");
            }
            catch (NpgsqlException exception)
            {
                if (exception.Message.Contains("violates foreign key constraint"))
                {
                    return this.Conflict("Coupon can't be deleted. Please contact administrator.");
                }

                return this.ServerError();
            }
        }

        /// <summary>
        /// The modify status of coupon.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Coupon status updated successfully.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPut]
        [Route("modify-status")]
        [ProducesResponseType(typeof(string), 200)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> ModifyStatusAsync([FromBody]CouponModel model, [FromHeader] LocationHeader header)
        {
            model = (CouponModel)EmptyFilter.Handler(model);
            var response = await this.couponsServices.ModifyStatusAsync(model.CouponId, Convert.ToInt32(model.ModifiedBy), model.Active);
            if (response == 0)
            {
                return this.ServerError();
            }

            var auditLogModel = new AuditLogModel
            {
                AccountId = model.ModifiedBy,
                LogTypeId = (int)LogTypes.Coupons,
                LogFrom = (int)AccountType.Administrator,
                LogDate = DateTime.UtcNow,
                LogDescription = $"{model.CouponCode} coupon status has been updated to {(model.Active ? "'Active'" : "'Inactive'")}.",
                LocationId = Convert.ToInt32(header.LocationId)
            };
            await this.auditLogServices.LogAsync(auditLogModel);

            return this.Success("Coupon status has been updated successfully.");
        }

        /// <summary>
        /// The apply coupon async.
        /// </summary>
        /// <param name="couponCode">
        /// The coupon code.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Applied coupon successfully.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPut]
        [Route("apply")]
        [ProducesResponseType(typeof(CouponModel), 200)]
        [ProducesResponseType(typeof(string), 400)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> ApplyCoupon(string couponCode)
        {
            if (string.IsNullOrEmpty(couponCode))
            {
                return this.BadRequest("Sorry! We don't have a coupon in the system with given coupon code.");
            }

            var coupon = await this.couponsServices.ApplyAsync(couponCode);
            return coupon == null || coupon.CouponId == 0 ? this.BadRequest("Sorry! We don't have a coupon in the system with given coupon code") : this.Success(coupon);
        }

        /// <summary>
        /// Check the coupon valid status and returns coupon model async.
        /// </summary>
        /// <param name="couponCode">
        /// The coupon code.
        /// </param>
        /// <param name="providerId">
        /// The provider id.
        /// </param>
        /// <param name="patientId">
        /// The patient id
        /// </param>
        /// <param name="providerCharges">
        /// Provider charges amount.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <remarks>
        /// ### REMARKS ###
        /// The following codes are returned
        /// - 200 - Coupon valid successfully.
        /// - 500 - Problem with Server side code.
        /// </remarks>
        [HttpPost]
        [Authorize]
        [Route("verify-and-get")]
        [ProducesResponseType(typeof(CouponModel), 200)]
        [ProducesResponseType(typeof(string), 400)]
        [ProducesResponseType(500)]
        public async Task<ActionResult> VerifyAndGetCoupon(string couponCode, int providerId, int patientId, decimal providerCharges)
        {
            if (string.IsNullOrEmpty(couponCode))
            {
                return this.BadRequest("Sorry! The given code is not valid.");
            }

            var coupon = await this.couponsServices.FetchActiveCoupon(couponCode);
            if (coupon == null)
            {
                return this.BadRequest("Sorry! The given code is not valid.");
            }

            var patientAppointments = await this.couponsServices.FetchPatientAppointments(patientId);

            if (coupon.AllowedForFirstAppointment == true && patientAppointments.Count() > 0)
                return this.BadRequest("Sorry! This code is valid for first appointment only.");

            if (coupon.NotAllowedProviderIds != null)
            {
                var providerIdsList = coupon.NotAllowedProviderIds.Split(",");
                foreach (var item in providerIdsList)
                {
                    if (item == providerId.ToString())
                    {
                        return this.BadRequest("Sorry! This code is not applicable for selected doctor.");
                    }
                }
            }

            if (coupon.NotAllowedPatientIds != null)
            {
                var patientIdsList = coupon.NotAllowedPatientIds.Split(",");
                foreach (var item in patientIdsList)
                {
                    if (item == patientId.ToString())
                    {
                        return this.BadRequest("Sorry! This code is not valid.");
                    }
                }
            }

            if (coupon.AllowedPerUser != null)
            {
                var thisCouponUsedCount = patientAppointments.Where(m => m.CouponId == coupon.CouponId).Count();
                if (thisCouponUsedCount >= coupon.AllowedPerUser)
                {
                    return this.BadRequest("Sorry! You have used this code maximum times.");
                }
            }

            decimal couponDiscountValue;
            decimal amountAfterDiscount;
            if (coupon.CouponType == 'A')
            {
                if (coupon.CouponValue >= providerCharges)
                {
                    couponDiscountValue = providerCharges;
                    amountAfterDiscount = 0;
                }
                else
                {
                    couponDiscountValue = coupon.CouponValue;
                    amountAfterDiscount = providerCharges - couponDiscountValue;
                }
            }
            else if (coupon.CouponType == 'P')
            {
                couponDiscountValue = (providerCharges * coupon.CouponValue) / 100;
                amountAfterDiscount = providerCharges - couponDiscountValue;
            }
            else
            {
                couponDiscountValue = 0;
                amountAfterDiscount = providerCharges;
            }

            CouponModel couponResponse = coupon;
            couponResponse.CouponDiscountValue = Convert.ToDecimal(couponDiscountValue.ToString("0.00"));
            couponResponse.AmountAfterDiscount = Convert.ToDecimal(amountAfterDiscount.ToString("0.00"));

            return this.Success(couponResponse);
        }
    }
}