using Coscine.Action;
using Coscine.Action.EventArgs;
using Coscine.Database.Models;
using Coscine.Database.ReturnObjects;
using Coscine.ApiCommons;
using Coscine.ApiCommons.Factories;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using Coscine.Configuration;
using Microsoft.AspNetCore.Authorization;
using Coscine.Database.Util;
using Coscine.Logging;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Coscine.Database.DataModel;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Coscine.Metadata;

namespace Coscine.Api.Project.Controllers
{
    [Authorize]
    public class ProjectController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly ProjectModel _projectModel;
        private readonly IConfiguration _configuration;
        private readonly Emitter _emitter;
        private readonly ActivatedFeaturesModel _activatedFeaturesModel;
        private readonly ProjectRoleModel _projectRoleModel;
        private readonly CoscineLogger _coscineLogger;
        private readonly AnalyticsLogObject _analyticsLogObject;

        public ProjectController(ILogger<ProjectController> logger)
        {
            _authenticator = new Authenticator(this, Program.Configuration);
            _configuration = Program.Configuration;
            _projectModel = new ProjectModel();
            _emitter = new Emitter(_configuration);
            _activatedFeaturesModel = new ActivatedFeaturesModel();
            _projectRoleModel = new ProjectRoleModel();
            _coscineLogger = new CoscineLogger(logger);
            _analyticsLogObject = new AnalyticsLogObject();
        }
        
        [Route("[controller]")]
        public IActionResult Index()
        {
            var user = _authenticator.GetUser();
            var result = _projectModel.GetWithAccess(user, UserRoles.Member, UserRoles.Owner).ToList()
                .Select((project) => _projectModel.CreateReturnObjectFromDatabaseObject(project))
                .OrderBy(element => element.DisplayName);

            if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
            {
                LogAnalytics("List Projects", result);
            }

            return Ok(result);
        }

        [Route("[controller]/-/topLevel")]
        public IActionResult GetTopLevelProjects()
        {
            var user = _authenticator.GetUser();
            var result = _projectModel.GetTopLevelWithAccess(user, UserRoles.Member, UserRoles.Owner).ToList()
                .Select((project) => _projectModel.CreateReturnObjectFromDatabaseObject(project))
                .OrderBy(element => element.DisplayName);

            if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
            {
                LogAnalytics("View Home", result);
            }

            return Ok(result);
        }

        [HttpGet("[controller]/{id}")]
        public IActionResult Get(string id)
        {
            var user = _authenticator.GetUser();
            var project = _projectModel.GetById(Guid.Parse(id));
            if (_projectModel.HasAccess(user, project, UserRoles.Member, UserRoles.Owner))
            {
                SubProjectModel subProjectModel = new SubProjectModel();
                var subProjectRel = subProjectModel.GetAllWhere((subProject) => subProject.SubProjectId == project.Id && project.Deleted == false);
                
                var parentProjectRelation = subProjectRel.FirstOrDefault();
                if (parentProjectRelation != null && _projectModel.HasAccess(user, parentProjectRelation.ProjectId, UserRoles.Member, UserRoles.Owner))
                {
                    return Ok(_projectModel.CreateReturnObjectFromDatabaseObject(project, parentProjectRelation.ProjectId));
                }
                return Ok(_projectModel.CreateReturnObjectFromDatabaseObject(project));
            }
            else
            {
                return Unauthorized($"User is not allowed to see given the project {id}");
            }
        }

        [HttpGet("[controller]/{id}/resources")]
        public IActionResult GetResources(string id)
        {
            var project = _projectModel.GetById(Guid.Parse(id));
            var user = _authenticator.GetUser();

            var resourceModel = new ResourceModel();
            var resourceTypeModel = new ResourceTypeModel();
            if (_projectModel.HasAccess(user, project, UserRoles.Member, UserRoles.Owner))
            {
                var resources = resourceModel.GetAllWhere((resource) =>
                        (from projectResource in resource.ProjectResourceResourceIdIds
                         where projectResource.ProjectId == project.Id
                         select projectResource).Any())
                        .Select((resource) =>
                        {
                            return resourceModel.CreateReturnObjectFromDatabaseObject(resource);
                        }).OrderBy(element => element.DisplayName);
                if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
                {
                    LogAnalytics("View Project", null, resources, id, user); // intentionally log as view project to help identify the related user action
                }
                return Json(resources);
            }
            else
            {
                return Unauthorized($"User is not allowed to see given the project {id}");
            }
        }

        [HttpGet("[controller]/{id}/quotas")]
        public IActionResult Quotas(string id)
        {
            var user = _authenticator.GetUser();
            var projectObject = ObjectFactory<ProjectObject>.DeserializeFromStream(Request.Body);
            var guidId = Guid.Parse(id);
            var project = _projectModel.GetById(guidId);
            if (_projectModel.HasAccess(user, project, UserRoles.Owner))
            {
                ProjectQuotaModel projectQuotaModel = new ProjectQuotaModel();
                var projectQuotas =
                    projectQuotaModel.GetAllWhere((projectQuota) =>
                        projectQuota.ProjectId == guidId
                        && projectQuota.ResourceType.Enabled == true)
                    .Select((projectQuota) => projectQuotaModel.CreateReturnObjectFromDatabaseObject(projectQuota));


                ResourceModel resourceModel = new ResourceModel();
                RDSResourceTypeModel rdsResourceTypeModel = new RDSResourceTypeModel();
                var returnList = new List<dynamic>();
                foreach (var projectQuota in projectQuotas)
                {
                    // TODO: Cleanup quota and give it to every resource, this hard coded solution seems not scalable
                    if (projectQuota.ResourceType.DisplayName == "rds") 
                    {
                        var resources = resourceModel.GetAllWhere((resource) =>
                                            resource.TypeId == projectQuota.ResourceType.Id
                                            && (from connection in resource.ProjectResourceResourceIdIds
                                                where connection.ProjectId == guidId
                                                select connection).Any());
                        
                        var size = resources.Sum((resource) => 
                                rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value).Size);
                        returnList.Add(new { 
                            type = projectQuota.ResourceType.DisplayName, 
                            available = projectQuota.Quotas,
                            allocated = size
                        });
                    }
                }
                return Json(returnList);
            }
            else
            {
                return Unauthorized("The user is not authorized to perform a get on the selected project!");
            }
        }

        [HttpPost("[controller]/{id}")]
        public IActionResult Update(string id)
        {
            var user = _authenticator.GetUser();
            var projectObject = ObjectFactory<ProjectObject>.DeserializeFromStream(Request.Body);
            var project = _projectModel.GetById(Guid.Parse(id));
            if (_projectModel.HasAccess(user, project, UserRoles.Owner))
            {
                LogAnalytics("Edit Project", null, null, id, user);
                return Ok(_projectModel.UpdateByObject(project, projectObject));
            }
            else
            {
                return Unauthorized("The user is not authorized to perform an update on the selected project!");
            }
        }

        [HttpDelete("[controller]/{id}")]
        public IActionResult Delete(string id)
        {
            var user = _authenticator.GetUser();
            var project = _projectModel.GetById(Guid.Parse(id));
            if (_projectModel.HasAccess(user, project, UserRoles.Owner))
            {
                LogAnalytics("Delete Project", null, null, id, user);
                DeleteProject(project);
                return Json(_projectModel.CreateReturnObjectFromDatabaseObject(project));
            }
            else
            {
                return Unauthorized("The user is not authorized to perform an update on the selected project!");
            }
        }

        public void DeleteProject(Database.DataModel.Project project, bool isHard = false, bool propegateAction = true)
        {
            var subProjectModel = new SubProjectModel();
            foreach (var subProject in subProjectModel.GetAllWhere(
                (subProject) => subProject.ProjectId == project.Id
                                && (subProject.SubProject_FK.Deleted == false || isHard)
            ))
            {
                Database.DataModel.Project subProjectObject;
                if (isHard)
                {
                    subProjectObject = _projectModel.GetByIdIncludingDeleted(subProject.SubProjectId);
                    subProjectModel.Delete(subProject);
                }
                else
                {
                    subProjectObject = _projectModel.GetById(subProject.SubProjectId);
                }
                DeleteProject(subProjectObject, isHard, propegateAction);
            }

            foreach (var subProject in subProjectModel.GetAllWhere((subProject) => subProject.SubProjectId == project.Id))
            {
                if (isHard)
                {
                    subProjectModel.Delete(subProject);
                }
            }

            if (isHard)
            {
                var projectResourceModel = new ProjectResourceModel();
                ResourceModel resourceModel = new ResourceModel();
                foreach (var projectResource in projectResourceModel.GetAllWhere((projectResource) => projectResource.ProjectId == project.Id))
                {
                    projectResourceModel.Delete(projectResource);
                    resourceModel.Delete(resourceModel.GetById(projectResource.ResourceId));
                }

                var projectRoleModel = new ProjectRoleModel();
                foreach (var projectRole in projectRoleModel.GetAllWhere((projectRole) => projectRole.ProjectId == project.Id))
                {
                    projectRoleModel.Delete(projectRole);
                }

                var projectDisciplineModel = new ProjectDisciplineModel();
                foreach (var projectDiscipline in projectDisciplineModel.GetAllWhere((projectDiscipline) => projectDiscipline.ProjectId == project.Id))
                {
                    projectDisciplineModel.Delete(projectDiscipline);
                }

                var projectInstituteModel = new ProjectInstituteModel();
                foreach (var projectInstitute in projectInstituteModel.GetAllWhere((projectInstitute) => projectInstitute.ProjectId == project.Id))
                {
                     projectInstituteModel.Delete(projectInstitute);
                }

                var projectQuotaModel = new ProjectQuotaModel();
                foreach (var projectQuota in projectQuotaModel.GetAllWhere((Quota) => Quota.ProjectId == project.Id))
                {
                    projectQuotaModel.Delete(projectQuota);
                }

                _activatedFeaturesModel.DeactivateAllFeatures(project);

                if (propegateAction)
                {
                    _emitter.EmitProjectDelete(new ProjectEventArgs(_configuration)
                    {
                        Project = project
                    });
                }

                _projectModel.HardDelete(project);
            }
            else
            {
                _projectModel.Delete(project);
            }
        }

        [HttpPost("[controller]")]
        public IActionResult Store()
        {
            var user = _authenticator.GetUser();
            var isRWTHMember = IsRWTHMember(user);
            var projectObject = ObjectFactory<ProjectObject>.DeserializeFromStream(Request.Body);

            if (projectObject.ParentId != null
                && projectObject.ParentId != new Guid()
                && !_projectModel.HasAccess(user, _projectModel.GetById(projectObject.ParentId), UserRoles.Owner))
            {
                return Unauthorized("User is not allowed to create SubProjects.");
            }

            var project = _projectModel.StoreFromObject(projectObject, user, isRWTHMember);

            if (projectObject.ParentId != null
                && projectObject.ParentId != new Guid()
                // for now, only an owner can add subprojects to projects
                && _projectModel.HasAccess(user, _projectModel.GetById(projectObject.ParentId), UserRoles.Owner))
            {
                var subProjectModel = new SubProjectModel();
                subProjectModel.LinkSubProject(projectObject.ParentId, project.Id);
            }

            _emitter.EmitProjectCreate(new ProjectEventArgs(_configuration)
            {
                Project = project,
                ProjectOwner = user
            });

            LogAnalytics("Add Project", null, null, project.Id.ToString(), user);

            return Json(_projectModel.CreateReturnObjectFromDatabaseObject(project));
        }

        private bool IsRWTHMember(User user)
        {
            var externalIds = new ExternalIdModel().GetAllWhere((externalId) => externalId.UserId == user.Id);
            if(externalIds.Count() == 0)
            {
                return false;
            }
            var externalIdList = new List<string>();
            
            foreach (var externalId in externalIds)
            {
                externalIdList.Add(externalId.ExternalIdColumn);
            }
            return new RdfStoreConnector(Program.Configuration.GetStringAndWait("coscine/local/virtuoso/additional/url")).GetTriples(new Uri("https://ror.org/04xfq0f34"), null, null, 1, externalIdList).Count() != 0;
        }

        private void LogAnalytics(string operation,
            IEnumerable<ProjectObject> projects = null,
            IEnumerable<ResourceObject> resources = null,
            string projectId = null,
            User user = null
        )
        {
            if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
            {
                _analyticsLogObject.Type = "Action";
                _analyticsLogObject.Operation = operation;

                if (projects != null)
                {
                    List<string> projectList = new List<string>();
                    foreach (var entry in projects)
                    {
                        projectList.Add(entry.Id.ToString());
                    }
                    _analyticsLogObject.ProjectList = projectList;
                }
                if (resources != null)
                {
                    List<string> shownResources = new List<string>();
                    foreach (var entry in resources)
                    {
                        shownResources.Add(entry.Id.ToString());
                    }
                    _analyticsLogObject.ResourceList = shownResources;
                }
                if (projectId != null)
                {
                    _analyticsLogObject.ProjectId = projectId;
                    if (user != null)
                    {
                        _analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), user.Id).ToString();
                    }
                }
                _coscineLogger.AnalyticsLog(_analyticsLogObject);
            }
        }
    }
}