using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Coscine.ApiCommons;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.AspNetCore.Authorization;
using Coscine.Database.Util;
using Microsoft.Extensions.Logging;
using Coscine.Database.Models;
using Coscine.Logging;

namespace Coscine.Api.Project.Controllers
{

    /// <summary>
    /// This controller represents the actions which can be taken with a search object
    /// </summary>
    [Authorize]
    public class SearchController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly ProjectRoleModel _projectRoleModel;
        private readonly CoscineLogger _coscineLogger;
        private readonly AnalyticsLogObject _analyticsLogObject;

        /// <summary>
        /// SearchController specifying a ProjectRoleModel and an AnalyticsObject
        /// </summary>
        /// <param name="logger">Logger</param>
        public SearchController(ILogger<SearchController> logger)
        {
            _authenticator = new Authenticator(this, Program.Configuration);
            _projectRoleModel = new ProjectRoleModel();

            _coscineLogger = new CoscineLogger(logger);
            _analyticsLogObject = new AnalyticsLogObject();
        }

        /// <summary>
        /// Searchs no filter
        /// </summary>
        /// <returns>Ok</returns>
        [HttpGet("[controller]/allNoFilter/")]
        public IActionResult SearchNoFilter()
        {
            var user = _authenticator.GetUser();
            return Ok(GetSearchResults(user.Id, "", ""));
        }

        /// <summary>
        /// Returns results for the search word
        /// </summary>
        /// <param name="encodedSearchWord">Encoded search word</param>
        /// <returns>Ok</returns>
        [HttpGet("[controller]/all/{encodedSearchWord}")]
        public IActionResult Search(string encodedSearchWord)
        {
            var user = _authenticator.GetUser();
            return Ok(GetSearchResults(user.Id, encodedSearchWord, ""));
        }

        /// <summary>
        /// Returns searched projects with no filter
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <returns>Ok</returns>
        [HttpGet("[controller]/projectNoFilter/{projectId}")]
        public IActionResult SearchProjectNoFilter(string projectId)
        {
            var user = _authenticator.GetUser();
            return Ok(GetSearchResults(user.Id, "", projectId));
        }

        /// <summary>
        /// Returns searched projects
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <param name="encodedSearchWord">Encoded search word</param>
        /// <returns>Ok</returns>
        [HttpGet("[controller]/project/{projectId}/{encodedSearchWord}")]
        public IActionResult SearchProject(string projectId, string encodedSearchWord)
        {
            var user = _authenticator.GetUser();
            return Ok(GetSearchResults(user.Id, encodedSearchWord, projectId));
        }

        /// <summary>
        /// This method gets the search results
        /// </summary>
        /// <param name="userId">Id of the user</param>
        /// <param name="encodedSearchWord">Encoded search word</param>
        /// <param name="projectId">Id of the project</param>
        /// <returns>Search results</returns>
        private JToken GetSearchResults(Guid userId, string encodedSearchWord, string projectId)
        {
            string searchQuery;
            if (!string.IsNullOrWhiteSpace(encodedSearchWord))
            {
                searchQuery = HttpUtility.UrlDecode(encodedSearchWord);
            }
            else
            {
                searchQuery = "";
            }

            List<Guid> list;
            if (projectId.Equals(""))
            {
                list = new List<Guid>();
            }
            else
            {
                list = GetAllSubProjects(projectId);
            }

            // create return object
            var json = new JObject();

            // search and add results for resources
            json["Resources"] = SearchForResources(userId, searchQuery, projectId, list);

            // search and add results for projects
            if (projectId.Equals(""))
            {
                json["Projects"] = SearchForProjects(userId, searchQuery, projectId, list, false);
            }
            else
            {
                json["Projects"] = new JArray();
            }

            // remove the id of the root project since it cann not be a subproject of it self
            if (list.Count >= 1)
            {
                list.RemoveAt(0);
            }

            // search and ad results for sub-projects
            json["SubProjects"] = SearchForProjects(userId, searchQuery, projectId, list, true);
            LogAnalytics("View Search Results", userId, projectId, json);
            return json;
        }

        /// <summary>
        /// This method gets all sub projects
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <returns>List of SubProjects</returns>
        private List<Guid> GetAllSubProjects(string projectId)
        {
            var list = new List<Guid>();
            if (!projectId.Equals(""))
            {
                list.Add(new Guid(projectId));
                var counter = 0;
                DatabaseConnection.ConnectToDatabase((db) =>
                {
                    while (counter != list.Count)
                    {
                        var innerResults = (from sp in db.SubProjects
                                            where sp.ProjectId.Equals(list[counter])
                                            select sp.SubProjectId);
                        list.AddRange(innerResults.ToList());
                        counter++;
                    }
                });
            }
            return list;
        }

        /// <summary>
        /// This method searchs projects
        /// </summary>
        /// <param name="userId">Id of the user</param>
        /// <param name="searchQuery">Search query</param>
        /// <param name="projectId">Id of the project</param>
        /// <param name="listOfSubprojects">List of subprojects</param>
        /// <param name="showSubProjects">Shows subprojects</param>
        /// <returns>Found projects</returns>
        private JToken SearchForProjects(Guid userId, string searchQuery, string projectId, List<Guid> listOfSubprojects, bool showSubProjects)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {
                var allSubProjects = (from sp in db.SubProjects select sp.SubProjectId).ToList();
                var allSubProjectsList = new List<Guid>();
                allSubProjectsList.AddRange(allSubProjects);

                var results =
                    (from p in db.Projects
                     join pr in db.ProjectRoles on p.Id equals pr.ProjectId into joinedPr
                     from jpr in joinedPr.DefaultIfEmpty()
                     join v in db.Visibilities on p.VisibilityId equals v.Id into joinedV
                     from jv in joinedV.DefaultIfEmpty()
                     join pd in db.ProjectDisciplines on p.Id equals pd.ProjectId into joinedPd
                     from jpd in joinedPd.DefaultIfEmpty()
                     join d in db.Disciplines on jpd.DisciplineId equals d.Id into joinedD
                     from jd in joinedD.DefaultIfEmpty()
                     join pi in db.ProjectInstitutes on p.Id equals pi.ProjectId into joinedPi
                     from jpi in joinedPi.DefaultIfEmpty()

                     where p.Deleted == false &&
                        ((!showSubProjects && !allSubProjectsList.Contains(p.Id)) ||
                         (showSubProjects && allSubProjectsList.Contains(p.Id))) &&
                         (jpr.UserId == userId || jv.DisplayName == "Public") &&
                         (projectId == "" || listOfSubprojects.Contains(p.Id)) &&
                         (searchQuery == "" ||
                         p.ProjectName.Contains(searchQuery) ||
                         p.Description.Contains(searchQuery) ||
                         p.Keywords.Contains(searchQuery) ||
                         p.DisplayName.Contains(searchQuery) ||
                         p.PrincipleInvestigators.Contains(searchQuery) ||
                         p.GrantId.Contains(searchQuery) ||
                         jv.DisplayName.Contains(searchQuery) ||
                         jd.Url.Contains(searchQuery) ||
                         jd.DisplayNameDe.Contains(searchQuery) ||
                         jd.DisplayNameEn.Contains(searchQuery))

                     select new { p.Id, p.DisplayName, p.Slug }).OrderBy(element => element.DisplayName).Distinct();
                return JToken.Parse(JsonConvert.SerializeObject(results));
            });
        }

        /// <summary>
        /// This method searchs for resources
        /// </summary>
        /// <param name="userId">Id of the user</param>
        /// <param name="searchQuery">Search Query</param>
        /// <param name="projectId">Id of the project</param>
        /// <param name="listOfSubprojects">List of subprojects</param>
        /// <returns>Found resources by a searchQuery</returns>
        private JToken SearchForResources(Guid userId, string searchQuery, string projectId, List<Guid> listOfSubprojects)
        {
            return DatabaseConnection.ConnectToDatabase((db) =>
            {

                var results = (from r in db.Resources
                               join pres in db.ProjectResources on r.Id equals pres.ResourceId into joinedPres
                               from jpres in joinedPres.DefaultIfEmpty()
                               join p in db.Projects on jpres.ProjectId equals p.Id into joinedP
                               from jp in joinedP.DefaultIfEmpty()
                               join pr in db.ProjectRoles on jp.Id equals pr.ProjectId into joinedPr
                               from jpr in joinedPr.DefaultIfEmpty()
                               join v in db.Visibilities on r.VisibilityId equals v.Id into joinedV
                               from jv in joinedV.DefaultIfEmpty()
                               join rd in db.ResourceDisciplines on r.Id equals rd.ResourceId into joinedRd
                               from jrd in joinedRd.DefaultIfEmpty()
                               join d in db.Disciplines on jrd.DisciplineId equals d.Id into joinedD
                               from jd in joinedD.DefaultIfEmpty()
                               join l in db.Licenses on r.LicenseId equals l.Id into joinedL
                               from jl in joinedL.DefaultIfEmpty()
                               join rt in db.ResourceTypes on r.TypeId equals rt.Id into joinedRt
                               from jrt in joinedRt.DefaultIfEmpty()

                               where jp.Deleted == false &&
                                   (jpr.UserId == userId || jv.DisplayName == "Public") &&
                                   (projectId == "" || listOfSubprojects.Contains(jd.Id)) &&
                                   (searchQuery == "" ||
                                   r.ResourceName.Contains(searchQuery) ||
                                   r.DisplayName.Contains(searchQuery) ||
                                   r.ResourceName.Contains(searchQuery) ||
                                   r.Keywords.Contains(searchQuery) ||
                                   r.UsageRights.Contains(searchQuery) ||
                                   r.Description.Contains(searchQuery) ||
                                   r.ApplicationProfile.Contains(searchQuery) ||
                                   jrt.DisplayName.Contains(searchQuery) ||
                                   jl.DisplayName.Contains(searchQuery) ||
                                   jd.DisplayNameDe.Contains(searchQuery) ||
                                   jd.DisplayNameEn.Contains(searchQuery))

                               select new { r.Id, r.DisplayName, jpr.ProjectId, jp.Slug }).OrderBy(element => element.DisplayName).Distinct();

                return JToken.Parse(JsonConvert.SerializeObject(results));

            });
        }

        /// <summary>
        /// Log Analytics
        /// </summary>
        /// <param name="operation">Operation</param>
        /// <param name="userId">Id of the user</param>
        /// <param name="projectId">Id of the project</param>
        /// <param name="json">AnalyticsLogObject</param>
        private void LogAnalytics(string operation, Guid userId, string projectId, JObject json)
        {
            if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
            {
                List<string> projects = new List<string>();
                foreach (var entry in json["Projects"])
                {
                    projects.Add(entry["Id"].ToString());
                }
                foreach (var entry in json["SubProjects"])
                {
                    projects.Add(entry["Id"].ToString());
                }
                List<string> resources = new List<string>();
                foreach (var entry in json["Resources"])
                {
                    resources.Add(entry["Id"].ToString());
                }

                _analyticsLogObject.Type = "Action";
                _analyticsLogObject.Operation = operation;
                if (!projectId.Equals(""))
                {
                    _analyticsLogObject.ProjectId = projectId;
                    _analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), userId).ToString();
                }
                _analyticsLogObject.ProjectList = projects;
                _analyticsLogObject.ResourceList = resources;
                _coscineLogger.AnalyticsLog(_analyticsLogObject);
            }
        }
    }
}