using Coscine.Api.Resources.ParameterObjects;
using Coscine.ApiCommons;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Database.ReturnObjects;
using Coscine.Database.Util;
using Coscine.Logging;
using Coscine.ResourceTypes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;

namespace Coscine.Api.Resources.Controllers
{
    /// <summary>
    /// This controller represents the actions which can be taken with a resource object.
    /// </summary>
    [Authorize]
    public class ResourceQuotaController : Controller
    {
        private readonly Authenticator _authenticator;

        private readonly CoscineLogger _coscineLogger;
        private readonly ResourceModel _resourceModel;
        private readonly ProjectModel _projectModel;
        private readonly ProjectRoleModel _projectRoleModel;
        private readonly ProjectQuotaModel _projectQuotaModel;
        private readonly ProjectResourceModel _projectResourceModel;
        private readonly ResourceTypeModel _resourceTypeModel;
        private readonly float _oneGiB = 1024 * 1024 * 1024; // 1 GiB = 1024 Bytes

        /// <summary>
        /// ResourceQuotaController constructor specifying a ResourceModel.
        /// </summary>
        public ResourceQuotaController(ILogger<ResourceQuotaController> logger)
        {
            _authenticator = new Authenticator(this, Program.Configuration);

            _coscineLogger = new CoscineLogger(logger);
            _resourceModel = new ResourceModel();
            _projectModel = new ProjectModel();
            _projectRoleModel = new ProjectRoleModel();
            _projectQuotaModel = new ProjectQuotaModel();
            _projectResourceModel = new ProjectResourceModel();
            _resourceTypeModel = new ResourceTypeModel();
        }

        /// <summary>
        /// Retrieves the resource used and reserved quota for an individual resource.
        /// </summary>
        /// <param name="id">Id of the resource.</param>
        /// <returns>The quota of the resource.</returns>
        [HttpGet("[controller]/{id}")]
        public ActionResult<ResourceQuotaReturnObject> GetResourceQuota(Guid id)
        {
            var resource = _resourceModel.GetById(id);

            if (resource == null)
            {
                return base.NotFound($"Could not find resource with id: {id}");
            }

            var projectId = _projectResourceModel.GetWhere(x => x.ResourceId == id).ProjectId;

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Resource: View Resource (RCV, Metadatamanager)
            if (!_projectModel.HasAccess(_authenticator.GetUser(), projectId, UserRoles.Owner, UserRoles.Member, UserRoles.Guest))
            {
                return Unauthorized("The user is not authorized to perform a get on the selected project!");
            }

            var displayName = _resourceTypeModel.GetById(resource.TypeId).DisplayName;

            var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource);

            if (resourceTypeDefinition == null)
            {
                return BadRequest($"No provider for: \"{displayName}\".");
            }

            return Json(Helpers.CreateResourceQuotaReturnObject(resource, resourceTypeDefinition));
        }

        /// <summary>
        /// Updates the resource quota for an individual resource.
        /// </summary>
        /// <param name="id">Id of the resource.</param>
        /// <param name="updateResourceObject">Contains the resource id and the new quota.</param>
        /// <returns>204 if successful.</returns>
        [HttpPost("[controller]/{id}")]
        public IActionResult UpdateResourceQuota(Guid id, [FromBody] UpdateResourceObject updateResourceObject)
        {
            var resource = _resourceModel.GetById(id);

            if (resource == null)
            {
                return NotFound($"Could not find resource with id: {id}");
            }

            var projectId = _projectResourceModel.GetWhere(x => x.ResourceId == id).ProjectId;
            var user = _authenticator.GetUser();

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Project: Change Settings (project, user, quota)
            if (!_projectModel.HasAccess(user, projectId, UserRoles.Owner) && resource.Creator != _authenticator.GetUser().Id)
            {
                return Unauthorized("The user is not authorized to perform a get on the selected project!");
            }

            var resourceType = _resourceTypeModel.GetById(resource.TypeId);

            if (resourceType == null)
            {
                return NotFound($"Could not find resourceType with id: {resource.TypeId}");
            }

            var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource);

            if (resourceTypeDefinition == null)
            {
                return BadRequest($"No provider for: \"{resource.Type.DisplayName}\".");
            }

            if (updateResourceObject.ReservedGiB < 1)
            {
                return BadRequest($"Allocated {updateResourceObject.ReservedGiB}. Cannot be less than 1.");
            }

            var projectQuota =
                _projectQuotaModel.GetWhere((x) =>
                    x.ProjectId == projectId &&
                    x.ResourceTypeId == resource.TypeId);

            var totalReserved = Helpers.CalculateTotalReservedQuota(resourceType, projectId);

            var reservedForCurrent = resourceTypeDefinition.GetResourceQuotaAvailable(resource.Id.ToString()).Result;
            var maximum = projectQuota.Quota - (totalReserved.Value - reservedForCurrent);

            if (totalReserved.Value - reservedForCurrent + updateResourceObject.ReservedGiB > projectQuota.Quota)
            {
                return BadRequest($"Cannot set quota to {updateResourceObject.ReservedGiB} GB. Current resource quota cannot reserve more than {maximum} GB. To increase that number, allocate more {resourceType.SpecificType} quota to the project.");
            }

            var used = (int)Math.Ceiling(resourceTypeDefinition.GetResourceQuotaUsed(resource.Id.ToString()).Result / _oneGiB);

            if (updateResourceObject.ReservedGiB < used)
            {
                return BadRequest($"Cannot set quota to {updateResourceObject.ReservedGiB} GB. Currently {used} GB are in use. You are not allowed to reserve less than what is currently in use.");
            }

            resourceTypeDefinition.SetResourceQuota(resource.Id.ToString(), updateResourceObject.ReservedGiB).Wait();

            if (resourceType.Type == "rds")
            {
                var rdsResourceTypeModel = new RDSResourceTypeModel();
                var rdsResourceType = rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
                rdsResourceTypeModel.Update(rdsResourceType);
            }
            else if (resourceType.Type == "rdss3")
            {
                var rdsS3ResourceTypeModel = new RdsS3ResourceTypeModel();
                var rdsS3ResourceType = rdsS3ResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value);
                rdsS3ResourceTypeModel.Update(rdsS3ResourceType);
            }

            if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
            {
                LogAnalyticsOwnerResourceQuotaChange(resource, user, updateResourceObject.ReservedGiB, (int)maximum);
            }

            return NoContent();
        }

        private void LogAnalyticsOwnerResourceQuotaChange(Resource resource, User user, int reserved, int maximum)
        {
            var resourceTypeDisplayName = _resourceTypeModel.GetById(resource.TypeId).DisplayName;
            var projectId = _projectResourceModel.GetProjectForResource(resource.Id).Value;

            _coscineLogger.AnalyticsLog(
                new AnalyticsLogObject
                {
                    Type = "Action",
                    Operation = "Owner Resource Quota Change",
                    RoleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id).ToString(),
                    ProjectId = projectId.ToString(),
                    QuotaSize = new List<string> { $"{resourceTypeDisplayName}: {reserved}/{maximum}" },
                    ResourceId = resource.Id.ToString(),
                    ApplicationsProfile = resource.ApplicationProfile
                });
        }
    }
}