using Coscine.Api.Project.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.Metadata;
using Coscine.ResourceTypes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Coscine.Api.Project.Controllers
{
    /// <summary>
    ///  /// This controller represents the actions which can be taken with a project object.
    /// </summary>
    [Authorize]
    public class ProjectQuotaController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly ProjectModel _projectModel;
        private readonly ProjectRoleModel _projectRoleModel;
        private readonly ProjectQuotaModel _projectQuotaModel;
        private readonly ResourceTypeModel _resourceTypeModel;
        private readonly ResourceModel _resourceModel;
        private readonly CoscineLogger _coscineLogger;
        private readonly RdfStoreConnector _rdfStoreConnector;

        /// <summary>
        /// ProjectQuotaController constructor
        /// </summary>
        /// <param name="logger">Logger</param>
        public ProjectQuotaController(ILogger<ProjectQuotaController> logger)
        {
            _authenticator = new Authenticator(this, Program.Configuration);
            _projectModel = new ProjectModel();
            _projectRoleModel = new ProjectRoleModel();
            _resourceTypeModel = new ResourceTypeModel();
            _resourceModel = new ResourceModel();
            _projectQuotaModel = new ProjectQuotaModel();
            _coscineLogger = new CoscineLogger(logger);
            _rdfStoreConnector = new RdfStoreConnector(Program.Configuration.GetString("coscine/local/virtuoso/additional/url"));
        }

        /// <summary>
        /// Retrieves all project quotas in GiB grouped by resource type for the selected project.
        /// </summary>
        /// <param name="id">Id of the project</param>
        /// <returns>List of project quotas per resource type</returns>
        [HttpGet("[controller]/{id}/-/all")]
        public ActionResult<IEnumerable<ProjectQuotaReturnObject>> Quotas(Guid id)
        {
            var user = _authenticator.GetUser();

            var project = _projectModel.GetById(id);

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

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Project: Create Subprojects | Project: View Settings (project, user, quota) ---> conflicts
            if (!_projectModel.HasAccess(user, project, UserRoles.Owner, UserRoles.Member))
            {
                return Unauthorized("The user is not authorized to perform a get on the selected project!");
            }

            var enabledResources = ResourceTypeFactory.Instance.GetResourceTypes();
            var resourceTypes = _resourceTypeModel.GetAllWhere(r => enabledResources.Any(e => r.SpecificType.Equals(e)));

            return Ok(resourceTypes.Select(x => Helpers.CreateProjectQuotaReturnObject(x, id)));
        }


        /// <summary>
        /// Retrieves the project quota in GiB of a resource type for the selected project together with all individual resources of this resource type.
        /// </summary>
        /// <param name="id">Id of the project</param>
        /// <param name="resourceTypeId">Id of the resource type</param>
        /// <returns>The project quota for the resource type together with all individual resources of this resource type.</returns>
        [HttpGet("[controller]/{id}/{resourceTypeId}")]
        public ActionResult<ProjectQuotaExtendedReturnObject> Quota(Guid id, Guid resourceTypeId)
        {
            var user = _authenticator.GetUser();

            var project = _projectModel.GetById(id);

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

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

            var resourceType = _resourceTypeModel.GetById(resourceTypeId);
            var enabledResources = ResourceTypeFactory.Instance.GetResourceTypes();

            if (!enabledResources.Contains(resourceType.SpecificType))
            {
                return base.NotFound($"Could not find resourceType with id: {resourceTypeId}");
            }

            return Ok(Helpers.CreateProjectQuotaExtendedReturnObject(resourceType, id));
        }

        /// <summary>
        /// Updates the reserved project quota of a resource type for a selected poject. Quota value in GiB.
        /// </summary>
        /// <param name="id">Id of the project</param>
        /// <param name="resourceTypeId">Id of the resource type</param>
        /// <param name="updateProjectQuotaObject">Object containing the update values.</param>
        /// <returns>NoContent (204).</returns>
        [HttpPost("[controller]/{id}/{resourceTypeId}")]
        public IActionResult UpdateQuota(Guid id, Guid resourceTypeId, [FromBody] UpdateProjectQuotaObject updateProjectQuotaObject)
        {
            var user = _authenticator.GetUser();

            var project = _projectModel.GetById(id);

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

            // 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, project, UserRoles.Owner))
            {
                return Unauthorized("The user is not authorized to perform a get on the selected project!");
            }

            var resourceType = _resourceTypeModel.GetById(resourceTypeId);
            var enabledResources = ResourceTypeFactory.Instance.GetResourceTypes();

            if (!enabledResources.Contains(resourceType.SpecificType))
            {
                return NotFound($"Could not find resourceType with id: {resourceTypeId}");
            }

            var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resourceType.Type, resourceType.SpecificType);

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

            if (!resourceTypeDefinition.GetResourceTypeInformation().Result.IsQuotaAdjustable)
            {
                return BadRequest($"Cannot adjust quota for {resourceType.DisplayName}.");
            }

            if (updateProjectQuotaObject.AllocatedGiB < 0)
            {
                return BadRequest($"Allocated {updateProjectQuotaObject.AllocatedGiB}. Cannot be less than 0 GB.");
            }

            var projectQuotaForCurrent = _projectQuotaModel.GetWhere(x => x.ProjectId == id && x.ResourceTypeId == resourceTypeId);
            var totalReserved = Helpers.CalculateTotalReservedQuota(resourceType, id);

            if (Helpers.ConvertCapacityUnits(totalReserved, QuotaUnit.GibiBYTE) > updateProjectQuotaObject.AllocatedGiB)
            {
                return BadRequest($"Cannot set quota ({updateProjectQuotaObject.AllocatedGiB} GB) below the total ({Helpers.ConvertCapacityUnits(totalReserved, QuotaUnit.GibiBYTE)} GB) that are currently reserved.");
            }

            if (updateProjectQuotaObject.AllocatedGiB > projectQuotaForCurrent.MaxQuota)
            {
                return BadRequest($"Cannot set quota to {updateProjectQuotaObject.AllocatedGiB} GB. It would exceed the maximum of {projectQuotaForCurrent.MaxQuota} GB for {resourceType.SpecificType} resources.");
            }

            var defaultQuotas = _rdfStoreConnector.GetQuotaDefault(user.Id.ToString());
            var defaultQuota = defaultQuotas.FirstOrDefault(q => q.ResourceType == resourceType.DisplayName);
            if (projectQuotaForCurrent == null)
            {
                var projectQuota = new ProjectQuota
                {
                    MaxQuota = (defaultQuota?.DefaultMaxQuota) ?? 0,
                    Quota = (defaultQuota?.DefaultQuota) ?? 0,
                    ProjectId = project.Id,
                    ResourceTypeId = resourceType.Id
                };
                _projectQuotaModel.Insert(projectQuota);
            }
            else
            {
                projectQuotaForCurrent.Quota = updateProjectQuotaObject.AllocatedGiB;
                _projectQuotaModel.Update(projectQuotaForCurrent);
            }

            if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
            {
                LogAnalyticsOwnerProjectQuotaChange(project, user);
            }

            return NoContent();
        }

        private void LogAnalyticsOwnerProjectQuotaChange(Database.DataModel.Project project, User user)
        {
            var enabledResources = ResourceTypeFactory.Instance.GetResourceTypes();
            var resourceTypes = _resourceTypeModel.GetAllWhere(r => enabledResources.Any(e => r.SpecificType.Equals(e)));

            var objects = resourceTypes.Select(x => Helpers.CreateProjectQuotaReturnObject(x, project.Id));

            _coscineLogger.AnalyticsLog(
                new AnalyticsLogObject
                {
                    Type = "Action",
                    Operation = "Owner Project Quota Change",
                    RoleId = _projectRoleModel.GetGetUserRoleForProject(project.Id, user.Id).ToString(),
                    ProjectId = project.Id.ToString(),
                    QuotaSize = objects.Select(x => $"{x.Name}: {x.Allocated.Value}/{x.Maximum.Value}").ToList()
                });
        }
    }
}