namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    using Dapper;

    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Hims.Shared.UserModels;
    using Hims.Shared.UserModels.Common;
    using Hims.Shared.UserModels.DashboardWidget;
    using Shared.EntityModels;

    /// <inheritdoc />
    public class DashboardWidgetServices : IDashboardWidgetService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        /// <inheritdoc cref="IDashboardWidgetService" />
        public DashboardWidgetServices(IUnitOfWork unitOfWork)
        {
            this.unitOfWork = unitOfWork;
        }

        /// <inheritdoc />
        public Task<IEnumerable<DashboardWidgetModel>> FetchAsync(DashboardWidgetFilterModel model)
        {
            try
            {
                var where = $@" WHERE 1 = 1 ";
                if ((bool)model.OnlyActive)
                {
                    where += $@"AND d.""Active"" is TRUE";
                }

                if (model.WidgetType!=null)
                {
                    where += $@" AND d.""DashboardWidgetTypeId""='{model.WidgetType}'";
                }
            
                if (model.RoleId != null)
                {
                    where += $@" AND dr.""RoleId"" = {model.RoleId}";
                }
                var query = $@"SELECT *, COUNT(A.""DashboardWidgetId"") OVER() ""TotalItems"" FROM (SELECT DISTINCT ON (d.""DashboardWidgetId"")
                                d.""DashboardWidgetId"",
	                            d.""Name"",
	                            d.""Icon"",
	                            d.""DashboardWidgetTypeId"",
	                            d.""DashboardWidgetCountTypeId"",
	                            c.""Name"" ""CountTypeName"",
	                            d.""StoredProcedureName"",
	                            dt.""Name"" ""Type"",
                                array_to_string( array(select wr.""RoleId"" from ""DashboardWidgetRole"" wr WHERE wr.""DashboardWidgetId"" = d.""DashboardWidgetId""), ',' ) AS ""RolesStr"",
	                            a.""FullName"" ""CreatedByName"",
	                            m.""FullName"" ""ModifiedByName"",
	                            d.""CreatedDate"",
                                d.""Active"",
	                            d.""ModifiedDate""
                            FROM
	                            ""DashboardWidget"" d
	                            JOIN ""DashboardWidgetType"" dt on  dt.""DashboardWidgetTypeId"" = d.""DashboardWidgetTypeId""
                                LEFT JOIN ""DashboardWidgetRole"" dr on  dr.""DashboardWidgetId"" = d.""DashboardWidgetId""
	                            JOIN ""Account"" a on a.""AccountId"" = d.""CreatedBy""
                                LEFT JOIN ""DashboardWidgetCountType"" c on c.""DashboardWidgetCountTypeId"" = d.""DashboardWidgetCountTypeId""
	                            LEFT JOIN ""Account"" m on m.""AccountId"" = d.""ModifiedBy"" {where}) A
                                order by A.""DashboardWidgetId"" desc";

                model.PageIndex -= 1;
                query += " LIMIT " + model.PageSize + " offset " + (model.PageIndex * model.PageSize);

                var result = this.unitOfWork.Current.QueryAsync<DashboardWidgetModel>(query);

                return result;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <inheritdoc />
        public async Task<GenericResponse> InsertAsync(DashboardWidgetModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();

            var testQuery = $@"SELECT * FROM ";
            try
            {
                var exec = testQuery + model.ExecSp;
                object response;
                switch (model.DashboardWidgetTypeId)
                {
                    case 1:
                        response = await this.unitOfWork.Current.QueryAsync(exec, transaction);
                        break;
                    case 2:
                        response = await this.unitOfWork.Current.QueryAsync(exec, transaction);
                        break;
                }
            }
            catch (Exception e)
            {
                transaction.Rollback();
                return new GenericResponse
                {
                    Status = GenericStatus.Warning,
                    Message = e.Message
                };
            }

            var query = $@"SELECT ""DashboardWidgetId"" from ""DashboardWidget"" WHERE UPPER(TRIM(""Name"")) = UPPER(TRIM('{model.Name}'))  AND ""Active"" IS TRUE";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query, transaction);
            if (isExists > 0)
            {
                transaction.Rollback();
                return new GenericResponse
                {
                    Status = GenericStatus.Info
                };
            }

            var widget = new DashboardWidget
            {
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.Now,
                Active = true,
                Name = model.Name,
                StoredProcedureName = model.StoredProcedureName,
                DashboardWidgetTypeId = model.DashboardWidgetTypeId,
                Icon = model.Icon,
                DashboardWidgetCountTypeId = model.DashboardWidgetCountTypeId
            };

            var widgetId = await this.unitOfWork.DashboardWidget.InsertAsync(widget, transaction);
            if (widgetId <= 0)
            {
                transaction.Rollback();
                return new GenericResponse
                {
                    Status = GenericStatus.Error
                };
            }

            if (model.Roles.Count > 0)
            {
                var roles = model.Roles.Select(x => new DashboardWidgetRole()
                {
                    DashboardWidgetId = widgetId,
                    RoleId = x
                });

                var insertResponse = await this.unitOfWork.DashboardWidgetRole.BulkInsertAsync(roles, transaction);
                if (insertResponse <= 0)
                {
                    transaction.Rollback();
                    return new GenericResponse
                    {
                        Status = GenericStatus.Error
                    };
                }
            }

            transaction.Commit();
            return new GenericResponse
            {
                Status = GenericStatus.Success
            };
        }

        public async Task<GenericResponse> UpdateAsync(DashboardWidgetModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var query = $@"SELECT ""DashboardWidgetId"" from ""DashboardWidget"" WHERE ""DashboardWidgetId"" = {model.DashboardWidgetId}";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query, transaction);

            if (isExists > 0)
            {
                var widget = await this.unitOfWork.DashboardWidget.FindAsync(x => x.DashboardWidgetId == model.DashboardWidgetId, transaction);

                widget.ModifiedBy = model.ModifiedBy;
                widget.ModifiedDate = DateTime.Now;
                widget.Name = model.Name;
                widget.StoredProcedureName = model.StoredProcedureName;
                widget.DashboardWidgetTypeId = model.DashboardWidgetTypeId;
                widget.Icon = model.Icon;
                widget.DashboardWidgetCountTypeId = model.DashboardWidgetCountTypeId;

                var updateResponse = await this.unitOfWork.DashboardWidget.UpdateAsync(widget, transaction);

                if (updateResponse <= 0)
                {
                    transaction.Rollback();
                    return new GenericResponse
                    {
                        Status = GenericStatus.Error
                    };
                }

                if (model.Roles.Count > 0)
                {
                    await this.unitOfWork.DashboardWidgetRole.DeleteAsync(x => x.DashboardWidgetId == widget.DashboardWidgetId);
                    
                    var roles = model.Roles.Select(x => new DashboardWidgetRole()
                    {
                        DashboardWidgetId = widget.DashboardWidgetId,
                        RoleId = x
                    });

                    var insertResponse = await this.unitOfWork.DashboardWidgetRole.BulkInsertAsync(roles, transaction);
                    if (insertResponse <= 0)
                    {
                        transaction.Rollback();
                        return new GenericResponse
                        {
                            Status = GenericStatus.Error
                        };
                    }
                }
            }

            transaction.Commit();
            return new GenericResponse
            {
                Status = GenericStatus.Success
            };
        }

        /// <inheritdoc />
        public async Task<int> DeactivateWidget(int id, int modifiedBy, string modifiedByName)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var query = $@"SELECT ""DashboardWidgetId"" from ""DashboardWidget"" WHERE ""DashboardWidgetId"" = {id}";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query, transaction);

            var result = 0;
            if (isExists > 0)
            {
                var widget = await this.unitOfWork.DashboardWidget.FindAsync(x => x.DashboardWidgetId == id, transaction);

                widget.ModifiedBy = modifiedBy;
                widget.ModifiedDate = DateTime.Now;
                widget.Active = false;

                result = await this.unitOfWork.DashboardWidget.UpdateAsync(widget, transaction);
            }

            if (result <= 0)
            {
                transaction.Rollback();
                return -1;
            }

            transaction.Commit();
            return result;
        }

        /// <inheritdoc />
        public async Task<int> ActivateWidget(int id, int modifiedBy, string modifiedByName)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var query = $@"SELECT ""DashboardWidgetId"" from ""DashboardWidget"" WHERE ""DashboardWidgetId"" = {id}";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query, transaction);

            var result = 0;
            if (isExists > 0)
            {
                var widget = await this.unitOfWork.DashboardWidget.FindAsync(x => x.DashboardWidgetId == id, transaction);

                widget.ModifiedBy = modifiedBy;
                widget.ModifiedDate = DateTime.Now;
                widget.Active = true;

                result = await this.unitOfWork.DashboardWidget.UpdateAsync(widget, transaction);
            }

            if (result <= 0)
            {
                transaction.Rollback();
                return -1;
            }

            transaction.Commit();
            return result;
        }

        /// <inheritdoc />
        public Task<string> FetchWidgetType(int dashboardWidgetTypeId)
        {
            var query = $@"select ""Name"" from ""DashboardWidgetType""  WHERE ""DashboardWidgetTypeId"" = {dashboardWidgetTypeId}";

            var type = this.unitOfWork.Current.QueryFirstOrDefaultAsync<string>(query);
            return type;
        }

        /// <inheritdoc />
        public async Task<GenericResponse> ExecuteAsync(ExecuteInputModel model)
        {
            var testQuery = $@"SELECT * FROM ";
            try
            {
                var exec = testQuery + model.ExecSp;
                object response = null;
                switch (model.DashboardWidgetTypeId)
                {
                    case 1:
                        var count = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<double>(exec);
                        return new GenericResponse
                        {
                            Status = GenericStatus.Success,
                            Data = count
                        };
                    case 2:
                    case 3:
                        var result = await this.unitOfWork.Current.QueryAsync<ExecuteChartModel>(exec);
                        return new GenericResponse
                        {
                            Status = GenericStatus.Success,
                            Data = result
                        };
                    case 5:
                        var records = await this.unitOfWork.Current.QueryAsync<object>(exec);
                        return new GenericResponse
                        {
                            Status = GenericStatus.Success,
                            Data = records
                        };

                }

                return new GenericResponse
                {
                    Status = GenericStatus.Success,
                    Data = response
                };
            }
            catch (Exception e)
            {
                return new GenericResponse
                {
                    Status = GenericStatus.Warning,
                    Message = e.Message
                };
            }
        }
    }
}