using Coscine.Action;
using Coscine.Action.EventArgs;
using Coscine.Api.Metadata.ParameterObjects;
using Coscine.ApiCommons;
using Coscine.ApiCommons.Exceptions;
using Coscine.Configuration;
using Coscine.Database.Models;
using Coscine.Database.Util;
using Coscine.Metadata;
using GitLabApiClient;
using GitLabApiClient.Models.Branches.Requests;
using GitLabApiClient.Models.Commits.Requests.CreateCommitRequest;
using GitLabApiClient.Models.MergeRequests.Requests;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using VDS.RDF;
using VDS.RDF.Writing;

namespace Coscine.Api.Metadata.Controllers
{
    /// <summary>
    /// This controller represents the actions which can be performed with a Metadata storage.
    /// </summary>
    [Authorize]
    public class MetadataController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly Emitter _emitter;
        private readonly IConfiguration _configuration;
        private readonly ResourceModel _resourceModel;
        private readonly RdfStoreConnector _rdfStoreConnector;
        private static readonly string HostUrl = "https://git.rwth-aachen.de/";
        private readonly string ApplicationProfileUrl = "https://purl.org/coscine/ap/";
        private static readonly string ApplicationProfileProjectURL = HttpUtility.UrlEncode("coscine/graphs/applicationprofiles");

        /// <summary>
        /// MetadataController constructor specifying an authenticator and a ResourceModel.
        /// </summary>
        public MetadataController()
        {
            _configuration = Program.Configuration;
            _authenticator = new Authenticator(this, _configuration);
            _resourceModel = new ResourceModel();
            _rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
            _emitter = new Emitter(_configuration);
        }

        /// <summary>
        /// This method returns all application profiles.
        /// </summary>
        /// <returns>profiles</returns>
        [HttpGet("[controller]/profiles/")]
        [AllowAnonymous]
        public IActionResult GetProfiles()
        {
            var applicationProfiles = _rdfStoreConnector.GetApplicationProfiles();
            return Json(new JArray(applicationProfiles));
        }

        /// <summary>
        /// This method returns the application profile for the given profileUrl.
        /// </summary>
        /// <param name="profile">Url of the application profile</param>
        /// <returns>Json with the application profile</returns>
        [HttpGet("[controller]/profiles/{profile}")]
        [AllowAnonymous]
        public IActionResult GetProfile(string profile)
        {
            var profileUrl = profile.StartsWith("http") ? HttpUtility.UrlDecode(profile) : $"{ApplicationProfileUrl}{profile}/";

            if (!profileUrl.StartsWith(ApplicationProfileUrl))
            {
                return StatusCode((int)HttpStatusCode.Forbidden, $"Profile has to start with {ApplicationProfileUrl}!");
            }

            var graph = _rdfStoreConnector.GetGraph(profileUrl);

            var tripleStore = new TripleStore();
            tripleStore.Add(graph);

            var outStoreJson = StringWriter.Write(tripleStore, new JsonLdWriter());

            var json = JToken.Parse(outStoreJson);

            return Ok(json);
        }

        /// <summary>
        /// This method returns a list of all vocabularies.
        /// </summary>
        /// <throws>Exception for the code that has not been implemented</throws>
        [HttpGet("[controller]/vocabularies/")]
        [AllowAnonymous]
        public IActionResult GetVocabularies()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method returns a specific vocabulary.
        /// </summary>
        /// <param name="path">Url of the vocabulary</param>
        /// <returns>JSON with the requested vocabulary</returns>
        [HttpGet("[controller]/vocabularies/{path}")]
        [AllowAnonymous]
        public ActionResult<BilingualLabels> GetVocabulary(string path)
        {
            var graph = _rdfStoreConnector.GetGraph(HttpUtility.UrlDecode(path));

            var bilingualLabels = RetrieveBilingualLabels(graph);

            return Ok(bilingualLabels);
        }

        /// <summary>
        /// This method returns instances.
        /// </summary>
        /// <param name="className">class name</param>
        /// <returns>instances as Json, or throw an Exception if the user has not beed authorized</returns>
        [HttpGet("[controller]/instances/{className}")]
        [AllowAnonymous]
        public ActionResult<BilingualLabels> GetClassInstances(string className)
        {
            if (!Uri.TryCreate(HttpUtility.UrlDecode(className), UriKind.Absolute, out Uri uri))
            {
                return BadRequest("ClassName is not a valid Uri.");
            }
            var graph = _rdfStoreConnector.GetClassGraph(uri);

            var bilingualLabels = RetrieveBilingualLabels(graph);

            return Ok(bilingualLabels);
        }

        /// <summary>
        /// Helper method which converts a graph to the described labels
        /// </summary>
        /// <param name="graph">RDF graph with labels</param>
        /// <returns>BilingualLabels</returns>
        private BilingualLabels RetrieveBilingualLabels(IGraph graph)
        {
            var bilingualLabels = new BilingualLabels();

            foreach (var kv in _rdfStoreConnector.GetVocabularyLabels(graph, "de"))
            {
                var label = new Label
                {
                    Value = kv.Key,
                    Name = kv.Value
                };
                bilingualLabels.De.Add(label);
            }

            foreach (var kv in _rdfStoreConnector.GetVocabularyLabels(graph, "en"))
            {
                var label = new Label
                {
                    Value = kv.Key,
                    Name = kv.Value
                };
                bilingualLabels.En.Add(label);
            }

            return bilingualLabels;
        }

        /// <summary>
        /// Create a request for storing a given application profile.
        /// </summary>
        /// <param name="applicationProfile">Object describing the application profile</param>
        /// <returns>NoContent</returns>
        [HttpPut("[controller]/profiles/")]
        public async Task<IActionResult> SaveApplicationProfileAsync([FromBody] ApplicationProfile applicationProfile)
        {
            var user = _authenticator.GetUser();

            if (string.IsNullOrWhiteSpace(user.EmailAddress))
            {
                return BadRequest("The user's email has to be set!");
            }

            var token = _configuration.GetStringAndWait("coscine/global/gitlabtoken");
            var gitLabClient = new GitLabClient(HostUrl, token);

            var newBranchName = "Request/" + Guid.NewGuid().ToString();

            if (!applicationProfile.BaseURL.StartsWith(ApplicationProfileUrl))
            {
                return BadRequest($"The Application Profile URL has to start with {ApplicationProfileUrl}.");
            }
            if (applicationProfile.MimeType.ToLower().Trim() != "text/turtle")
            {
                return BadRequest("The mime type has to be text/turtle");
            }

            await gitLabClient.Branches.CreateAsync(ApplicationProfileProjectURL, new CreateBranchRequest(newBranchName, "master"));

            var folderName = applicationProfile.BaseURL.Replace(ApplicationProfileUrl, "");
            if (folderName.EndsWith("/"))
            {
                folderName = folderName[..(folderName.Length - 1)];
            }

            var actions = new List<CreateCommitRequestAction>
            {
                new CreateCommitRequestAction(
                    new CreateCommitRequestActionType(), 
                    $"profiles/{folderName}/index.ttl") { 
                        Content = applicationProfile.Definition 
                    }
            };

            var commitRequest = new CreateCommitRequest(newBranchName, $"{user.DisplayName} requests new application profile {applicationProfile.Name}", actions)
            {
                AuthorEmail = user.EmailAddress,
                AuthorName = user.DisplayName
            };

            await gitLabClient.Commits.CreateAsync(ApplicationProfileProjectURL, commitRequest, false);

            var newMergeRequest = await gitLabClient.MergeRequests.CreateAsync(ApplicationProfileProjectURL,
                new CreateMergeRequest(newBranchName, "master", $"Merge request for new application profile {applicationProfile.Name}")
                {
                    Description = $"Merge request created by {user.DisplayName}, {user.EmailAddress}, created at {DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss tt")}"
                });

            _emitter.EmitApplicationProfileRequest(new ApplicationProfileEventArgs(_configuration)
            {
                RequestOwner = user,
                ApplicationProfileName = applicationProfile.Name,
                MergeRequestURL = newMergeRequest.WebUrl
            });

            return NoContent();
        }
    }
}