using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using BO.AppServer.Business.Exceptions;
using BO.AppServer.Business.Mapper;
using BO.AppServer.Business.Services.Base;
using BO.AppServer.Data.Entity;
using BO.AppServer.Data.Repository;
using BO.AppServer.Metadata.Configuration;
using BO.AppServer.Metadata.Dto;
using BO.AppServer.Metadata.Enums;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Data.Interface;
using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
using Quadarax.Foundation.Core.Exceptions;

namespace BO.AppServer.Business.Services
{
    public class WorkspaceService: UserContextService
    {
        #region *** Private fields ***
        private readonly WorkspaceRepo _repoWorkspace;
        private readonly BillingPlanRepo _repoBillingPlan;
        private readonly WorkspaceBillingRepo _repoWorkspaceBilling;
        #endregion

        #region *** Constructor ***
        public WorkspaceService(WorkspaceRepo repoWorkspace, 
            BillingPlanRepo repoBillingPlan,
            WorkspaceBillingRepo repoWorkspaceBilling,
            IOptions<Configuration> config,
            UserRepo repoUser, 
            UserManager<IdentityUser> userManager, 
            IPrincipal currentPrincipal, 
            ILoggerFactory logger) : base(config, repoUser, userManager, currentPrincipal, logger)
        {
            _repoWorkspace = repoWorkspace ?? throw new ArgumentNullException(nameof(repoWorkspace));
            _repoBillingPlan = repoBillingPlan ?? throw new ArgumentNullException(nameof(repoBillingPlan));
            _repoWorkspaceBilling = repoWorkspaceBilling ??  throw new ArgumentNullException(nameof(repoWorkspaceBilling));
        }
        #endregion


        #region *** Public Operations ***

        public async Task<ResultValueDto<WorkspaceRDto>> CreateWorkspaceAsync(WorkspaceCDto workspace)
        {
            var billingPlan = _repoBillingPlan.GetByCode(workspace.BillingPlanCode);
            if (billingPlan == null)
            {
                var exc = new EntityNotExistsException(typeof(BillingPlan), workspace.BillingPlanCode, 0);
                Log.LogWarning(exc.Message);
                throw exc;
            }

            var owner = _repoUser.GetByName(workspace.AssignedUserName);
            if (owner == null)
            {
                var exc = new EntityNotExistsException(typeof(User), workspace.AssignedUserName, 0);
                Log.LogWarning(exc.Message);
                throw exc;
            }


            var current = GetCurrentUser();
            var newWorkspace = _repoWorkspace.CreateWorkspace(workspace.Name, billingPlan, workspace.Apipassword, true, owner,current);
            _repoWorkspace.Commit();
            Log.LogInformation($"Workspace '{newWorkspace.Name}' [API-KEY:{newWorkspace.Apikey}] created and appends to users: {string.Join(",", newWorkspace.UserWorkspaces.Select(x=>x.User.Name))}");
            return new ResultValueDto<WorkspaceRDto>(newWorkspace.Map<Workspace, WorkspaceRDto>());
        }

        public async Task<ResultsValueDto<WorkspaceRDto>> GetWorkspacesAsync(WorkspaceScopeEnums scope, PagingDto paging,string ownerUserName)
        {
            List<Workspace> workspaces = null;
            switch (scope)
            {
                case WorkspaceScopeEnums.All:
                    workspaces = _repoWorkspace.Query(paging).ToList();
                    break;
                case WorkspaceScopeEnums.Empty:
                    workspaces = _repoWorkspace.GetAllEmpty(paging).ToList();
                    break;
                case WorkspaceScopeEnums.NotEmpty:
                    workspaces = _repoWorkspace.GetAllNonEmpty(paging).ToList();
                    break;
                case WorkspaceScopeEnums.My:
                    var owner = _repoUser.GetByName(ownerUserName);
                    if (owner == null)
                    {
                        var exc = new EntityNotExistsException(typeof(User), ownerUserName, 0);
                        Log.LogWarning(exc.Message);
                        throw exc;
                    }
                    workspaces = _repoWorkspace.GetAllByOwner(owner, paging).ToList();
                    break;
                case WorkspaceScopeEnums.MyDefault:
                    owner = _repoUser.GetByName(ownerUserName);
                    if (owner == null)
                    {
                        var exc = new EntityNotExistsException(typeof(User), ownerUserName, 0);
                        Log.LogWarning(exc.Message);
                        throw exc;
                    }
                    var workspace = _repoWorkspace.GetDefaultByOwner(owner);
                    if (workspace == null)
                    {
                        var exc = new CodeException(1410,$"User '{ownerUserName}' has any default workspace set!");
                        Log.LogWarning(exc.Message);
                        throw exc;
                    }

                    workspaces = new List<Workspace>(new[] { workspace });
                    break;
            }

            return new ResultsValueDto<WorkspaceRDto>(workspaces.MapList<Workspace, WorkspaceRDto>());
        }

        public async Task<long> GetWorkspaceIdByAPIAccess(string apiKey, string apiPassword, string currentUserName)
        {
            var owner = string.IsNullOrEmpty(currentUserName) ? GetCurrentUser() : _repoUser.GetByName(currentUserName);
            if (owner == null)
            {
                var exc = new EntityNotExistsException(typeof(User), currentUserName, 0);
                Log.LogWarning(exc.Message);
                throw exc;
            }
            if (!Guid.TryParse(apiKey, out var apiKeyGuid))
                apiKeyGuid = Guid.Empty;

            Workspace workspace = null;
            if (await HasUserElevation(currentUserName))
            {
                // if elevated user and system user
                workspace = _repoWorkspace.GetByApiKey(apiKey);
                if (workspace.Apipassword != apiPassword)
                    workspace = null;
            }
            else
            {
                // for rest
                workspace = _repoWorkspace.GetAllByOwner(owner, new Paging())
                    .FirstOrDefault(x => x.Apikey == apiKeyGuid && x.Apipassword == apiPassword);
            }


            if (workspace == null)
            {
                var exc = new CodeException(1400, $"Workspace with APIKey '{apiKey}' not exists or API password is not valid!");
                Log.LogWarning(exc.Message + $"\nUser Context:'{owner.Name}' [{owner.Id}]");
                throw exc;
            }

            return workspace.Id;
        }

        public async Task<ResultValueDto<WorkspaceRDto>> GetWorkspace(WorkspaceIdentificationTypeEnum identificationType, string identificationValue)
        {
            var workspace = identificationType == WorkspaceIdentificationTypeEnum.Id ? 
                _repoWorkspace.Get(ArgumentAsLong(nameof(identificationValue), identificationValue)) 
                : identificationType == WorkspaceIdentificationTypeEnum.ApiKey ? _repoWorkspace.GetByApiKey(identificationValue) 
                    : _repoWorkspace.GetByName(identificationValue);

            
            if (workspace == null)
                throw new NoDataException();

            var currentBillingPlan = GetCurrentWorkspaceBillingOrCreate(workspace.Id);

            // gather rest of data
            var result = new ResultValueDto<WorkspaceRDto>(workspace.Map<Workspace, WorkspaceRDto>());
            result.Value.CreditAmount = currentBillingPlan.ValueNumber;
            result.Value.BillingPlanCode = currentBillingPlan.BillingPlan.Code;
            return result;
        }

        public async Task<bool> ExistsWorkspace(WorkspaceIdentificationTypeEnum identificationType, string identificationValue)
        {
            var workspace = identificationType == WorkspaceIdentificationTypeEnum.Id ? 
                _repoWorkspace.Get(ArgumentAsLong(nameof(identificationValue), identificationValue)) 
                : identificationType == WorkspaceIdentificationTypeEnum.ApiKey ? _repoWorkspace.GetByApiKey(identificationValue) 
                    : _repoWorkspace.GetByName(identificationValue);


            return workspace != null;
        }


        #endregion


        #region *** Private Operations ***

        private WorkspaceBilling GetCurrentWorkspaceBillingOrCreate(long workspaceId)
        {
            var currentBillingPlan = _repoWorkspaceBilling.GetByCurrentForWorkspace(workspaceId);
            if (currentBillingPlan == null)
            {
                currentBillingPlan = _repoWorkspaceBilling.CreateForWorkspace(workspaceId,
                    _repoBillingPlan.GetByCode(_configuration.Business.DefaultBillingPlanCode));
                _repoWorkspaceBilling.Commit();
                Log.LogWarning($"Default billing plan was not set for workspace [{workspaceId}]. Set default billing plan '{currentBillingPlan.BillingPlan.Code}'.");
            }

            return currentBillingPlan;
        }
        #endregion
    }
}
