using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using Sockeye.Models;
using Sockeye.Api.ControllerHelpers;
using Sockeye.Biz;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Sockeye.Util;

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

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

        /// <summary>
        /// Pre-login route to confirm server is available
        /// </summary>       
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet("hello")]
        public async Task<IActionResult> GetPreLoginPing()
        {
        
            //confirm if there are logo's to show as well
            var logo = await ct.Logo.AsNoTracking().SingleOrDefaultAsync();

            bool HasLargeLogo = false;
            bool HasMediumLogo = false;
            bool HasSmallLogo = false;
            if (logo != null)
            {
                if (logo.Small != null) HasSmallLogo = true;
                if (logo.Medium != null) HasMediumLogo = true;
                if (logo.Large != null) HasLargeLogo = true;
            }
 

            return Ok(ApiOkResponse.Response(
                  new {  ll = HasLargeLogo, ml = HasMediumLogo, sl = HasSmallLogo }));
        }



        /// <summary>
        /// Get count of new notifications waiting
        /// </summary>       
        /// <returns></returns>        
        [HttpGet("new-count")]
        public async Task<IActionResult> GetNewCount()
        {
            var UserId = UserIdFromContext.Id(HttpContext.Items);
            if (serverState.IsClosed && UserId != 1)//bypass for superuser to fix fundamental problems
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

            return Ok(ApiOkResponse.Response(await ct.InAppNotification.CountAsync(z => z.UserId == UserId && z.Fetched == false)));
        }

        /// <summary>
        /// Get all in-app notifications
        /// </summary>       
        /// <returns></returns>        
        [HttpGet("app-notifications")]
        public async Task<IActionResult> GetAppNotifications()
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            var UserId = UserIdFromContext.Id(HttpContext.Items);
            var ret = await ct.InAppNotification.AsNoTracking().Where(z => z.UserId == UserId).OrderByDescending(z => z.Created).ToListAsync();
            await ct.Database.ExecuteSqlInterpolatedAsync($"update ainappnotification set fetched={true} where userid = {UserId}");
            return Ok(ApiOkResponse.Response(ret));
        }


        /// <summary>
        /// Delete app Notification
        /// </summary>
        /// <param name="id"></param>
        /// <returns>NoContent</returns>
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteAppNotification([FromRoute] long id)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            var UserId = UserIdFromContext.Id(HttpContext.Items);
            var n = await ct.InAppNotification.FirstOrDefaultAsync(z => z.Id == id);
            if (n == null)
                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id"));
            if (n.UserId != UserId)
                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "Can't delete notification for another user"));
            ct.InAppNotification.Remove(n);
            await ct.SaveChangesAsync();
            return NoContent();
        }


        /// <summary>
        /// Get Notify Event object list from queue
        /// </summary>     
        /// <returns>Notify Event objects awaiting delivery</returns>
        [HttpGet("queue")]
        public async Task<IActionResult> GetQueue()
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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

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

            var ret = new List<NotifyEventQueueItem>();
            var NotifyEvents = await ct.NotifyEvent.AsNoTracking().ToListAsync();
            foreach (NotifyEvent ne in NotifyEvents)
            {
                var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == ne.UserId).Select(x => new { Active = x.Active, Name = x.Name }).FirstOrDefaultAsync();
                var Subscription = await ct.NotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == ne.NotifySubscriptionId);

                ret.Add(new NotifyEventQueueItem(ne.Id, ne.Created, ne.EventDate, (ne.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice), ne.UserId, UserInfo.Name, ne.EventType, ne.SockType, ne.Name));
            }

            return Ok(ApiOkResponse.Response(ret));
        }

        public record NotifyEventQueueItem(long Id, DateTime Created, DateTime EventDate, DateTime DeliverAfter, long UserId, string User, NotifyEventType EventType, SockType SockType, string Name);

        /// <summary>
        /// Delete pending notification event
        /// </summary>
        /// <param name="id"></param>
        /// <returns>NoContent</returns>
        [HttpDelete("notify-event/{id}")]
        public async Task<IActionResult> DeleteNotifyEvent([FromRoute] long id)
        {
            if (!serverState.IsOpen)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
            if (!ModelState.IsValid)
                return BadRequest(new ApiErrorResponse(ModelState));
            if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.OpsNotificationSettings))
            {
                return StatusCode(403, new ApiNotAuthorizedResponse());
            }
            var n = await ct.NotifyEvent.FirstOrDefaultAsync(z => z.Id == id);
            if (n == null)
                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id"));
            ct.NotifyEvent.Remove(n);
            await ct.SaveChangesAsync();
            return NoContent();
        }



        /// <summary>
        /// Send direct message notification to selected users
        /// </summary>       
        /// <returns>NoContent on success or error</returns>        
        [HttpPost("direct-message")]
        public async Task<IActionResult> SendNotifyDirectMessage([FromBody] NotifyDirectMessage notifyDirectMessage)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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


            foreach (long l in notifyDirectMessage.Users)
            {
                if (l != 0)
                    await NotifyEventHelper.AddGeneralNotifyEvent(
                        NotifyEventType.GeneralNotification, notifyDirectMessage.Message, UserNameFromContext.Name(HttpContext.Items), null, l
                        );
            }

            return NoContent();
        }

        public class NotifyDirectMessage
        {
            public NotifyDirectMessage()
            {
                Users = new List<long>();
            }
            [Required]
            public string Message { get; set; }

            [Required]
            public List<long> Users { get; set; }
        }

        /// <summary>
        /// Send direct SMTP message notification so single object / address
        /// Server notification settings must be set and active
        /// Currently supported types are Customer, HeadOffice, Vendor, User
        /// WARNING: be careful using this method; high volume emailing or spam-like behavior
        /// could result in a ban or block of your mail account or mail server or domain
        /// Use of this method is logged to Sockeye event log on successful attempted delivery
        /// </summary>       
        /// <returns>Accepted on success or error</returns>        
        [HttpPost("direct-smtp")]
        public async Task<IActionResult> SendNotifySmtpDirectMessage([FromBody] NotifyDirectSMTP notifyDirectSMTP)
        {
            if (serverState.IsClosed)
                return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));

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

            if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive)
                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "Email notifications are set to OFF at server, unable to send 'on request' type SMTP notification"));

            //for now I'm allowing any authenticated user to call this route intentionally as the use case is for our own internal messaging from the Customer form only or for API users who want to send 
            //email when rendering a report (case 4310).

            //validate incoming data
            if (string.IsNullOrWhiteSpace(notifyDirectSMTP.Subject))
                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "Subject", "Subject field is required"));

            if (string.IsNullOrWhiteSpace(notifyDirectSMTP.TextBody) && string.IsNullOrWhiteSpace(notifyDirectSMTP.HTMLBody))
                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "TextBody or HTMLBody field is required"));

            if (string.IsNullOrWhiteSpace(notifyDirectSMTP.ToAddress))
            {
                //We need to fetch the address from the object type and id
                //if no id then can skip the rest here
                if (notifyDirectSMTP.ObjectId == 0)
                {
                    return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ObjectId", "No address or object id specified, no where to send this"));
                }
                //get the address
                switch (notifyDirectSMTP.SockType)
                {
                    case SockType.NoType:
                        return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ToAddress", "No address or object type and id specified no where to send this"));
                    case SockType.Customer:
                        {
                            var o = await ct.Customer.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync();
                            if (o == null)
                            {
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
                            }
                            if (string.IsNullOrWhiteSpace(o.EmailAddress))
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Customer {o.Name} doesn't have an email address no where to send this"));
                            if (o.Active == false)
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Customer {o.Name} is not active, only active customers can be emailed directly"));
                            notifyDirectSMTP.ToAddress = o.EmailAddress;
                        }
                        break;
                    case SockType.HeadOffice:
                        {
                            var o = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync();
                            if (o == null)
                            {
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
                            }
                            if (string.IsNullOrWhiteSpace(o.EmailAddress))
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"HeadOffice {o.Name} doesn't have an email address no where to send this"));
                            if (o.Active == false)
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"HeadOffice {o.Name} is not active, only active head offices can be emailed directly"));
                            notifyDirectSMTP.ToAddress = o.EmailAddress;
                        }
                        break;
                    
                    case SockType.User:
                        {
                            var o = await ct.User.Include(z => z.UserOptions).AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.UserOptions.EmailAddress, x.Active }).FirstOrDefaultAsync();
                            if (o == null)
                            {
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
                            }
                            if (string.IsNullOrWhiteSpace(o.EmailAddress))
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Vendor {o.Name} doesn't have an email address no where to send this"));
                            if (o.Active == false)
                                return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Vendor {o.Name} is not active, only active Vendors can be emailed directly"));
                            notifyDirectSMTP.ToAddress = o.EmailAddress;
                        }
                        break;

                    default:
                        return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "SockType", "Specified Type not supported for 'on request' smtp"));

                }
            }
            var UserId = UserIdFromContext.Id(HttpContext.Items);

            IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
            try
            {
                await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, notifyDirectSMTP.HTMLBody);
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
            }
            catch (Exception ex)
            {
                await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex);
                return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, ExceptionUtil.ExtractAllExceptionMessages(ex)));
            }

            return Accepted();
        }

        public class NotifyDirectSMTP
        {
            public NotifyDirectSMTP()
            {

            }

            public long ObjectId { get; set; } = 0;
            public SockType SockType { get; set; } = SockType.NoType;
            public string ToAddress { get; set; }
            public string Subject { get; set; }
            public string TextBody { get; set; }
            public string HTMLBody { get; set; }
        }

        //------------


    }//eoc
}//eons