using Coscine.Api.Project.Models;
using Coscine.ApiCommons;
using Coscine.ApiCommons.Exceptions;
using Coscine.ApiCommons.Factories;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using VDS.RDF.Writing;
using VDS.RDF.Parsing;
using VDS.RDF;
using Metadata;
using System.Web;
using System.IO;

namespace Coscine.Api.Project.Controllers
{
    public class MetadataController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly MetadataModel _metadataModel;
        private readonly ResourceModel _resourceModel;
        private readonly Util _util;

        public MetadataController()
        {
            _authenticator = new Authenticator(this, Program.Configuration);
            _metadataModel = new MetadataModel();
            _resourceModel = new ResourceModel();
            _util = new Util();
        }

        [Route("[controller]")]
        public IActionResult Index()
        {
            return Ok(_authenticator.ValidateAndExecute((user) =>
            {
                return NoContent();
            }));
        }

        [HttpGet("[controller]/resource/{resourceId}/ap/{applicationProfileId}")]
        public IActionResult GetApplicationProfile(string resourceId, string applicationProfileId)
        {
            var user = _authenticator.GetUserFromToken();

            var resource = _resourceModel.GetById(Guid.Parse(resourceId));
            if (_metadataModel.IsProjectMember(user, resource))
            {
                var graph = _util.GetGraph(HttpUtility.UrlDecode(applicationProfileId));
                var fixedValuesGraph = new Graph();

                fixedValuesGraph.LoadFromString(resource.FixedValues, new RdfJsonParser());

                graph.Merge(fixedValuesGraph);

                var json = JToken.Parse(VDS.RDF.Writing.StringWriter.Write(graph, new RdfJsonWriter()));
                
                return Ok(json);
            }
            else
            {
                throw new NotAuthorizedException("User is no project member!");
            }

        }

        [HttpGet("[controller]/resource/{resourceId}/aplist/")]
        public IActionResult ListAllApplicationProfiles(string resourceId)
        {
            return Ok(_authenticator.ValidateAndExecute((user) =>
            {
                var resource = _resourceModel.GetById(Guid.Parse(resourceId));
                if (_metadataModel.IsProjectMember(user, resource))
                {
                    var graphUris = _util.ListGraphs();

                    return new JArray(graphUris.Select(x => x.ToString()).Where(x => x.StartsWith("https://purl.org/coscine/ap/")));
                }
                else
                {
                    throw new NotAuthorizedException("User is no project member!");
                }
            }));
        }

        [HttpGet("[controller]/resource/{resourceId}/filename/{filename}/ver/{version}")]
        public IActionResult GetMetadataForFile(string resourceId, string filename, string version)
        {
            return Ok(_authenticator.ValidateAndExecute((user) =>
            {
                var resource = _resourceModel.GetById(Guid.Parse(resourceId));
                if (_metadataModel.IsProjectMember(user, resource))
                {
                    var id = _metadataModel.GenerateId(resourceId, filename, version);
                    var graph = _util.GetGraph(id);
                    return JToken.Parse(VDS.RDF.Writing.StringWriter.Write(graph, new RdfJsonWriter()));
                }
                else
                {
                    throw new NotAuthorizedException("User is no project member!");
                }
            }));
        }

        [HttpPut("[controller]/resource/{resourceId}/filename/{filename}/ver/{version}")]
        public IActionResult StoreMetadataForFile(string resourceId, string filename, string version)
        {
            return Ok(_authenticator.ValidateAndExecute((user) =>
            {
                var innerBlock = ObjectFactory<JToken>.DeserializeFromStream(Request.Body);
                var graphName = _metadataModel.GenerateId(resourceId, filename, version);
                var graphNameUri = new Uri(graphName);
                var json = new JObject
                {
                    [graphName] = innerBlock
                };

                var resource = _resourceModel.GetById(Guid.Parse(resourceId));
                if (_metadataModel.IsProjectMember(user, resource))
                {
                    var graph = new Graph();
                    graph.LoadFromString(json.ToString(), new RdfJsonParser());

                    var fixedValuesGraph = new Graph();
                    fixedValuesGraph.LoadFromString(resource.FixedValues, new RdfJsonParser());

                    foreach(var triple in fixedValuesGraph.Triples.Where(x => x.Predicate.ToString() == "https://purl.org/coscine/fixedValue"))
                    {
                        // Remove any existing triples
                        foreach (var triple2 in graph.GetTriplesWithSubjectPredicate(graph.CreateUriNode(graphNameUri), triple.Subject).ToList())
                        {
                            graph.Retract(triple2);
                        }
                        graph.Assert(graph.CreateUriNode(graphNameUri), triple.Subject, triple.Object);
                    }

                    // Default values is not checked or added

                    // validate the data
                    if (_util.ValidateShacl(graph, graphNameUri))
                    {
                        // store the data
                        if (_util.HasGraph(graphNameUri))
                        {
                            _util.ClearGraph(graphNameUri);
                        }
                        else
                        {
                            _util.CreateNamedGraph(graphNameUri);
                        }

                        // BaseUri must be set for the sparql query
                        graph.BaseUri = graphNameUri;
                        _util.AddGraph(graph);

                        return NoContent();
                    }
                    else
                    {
                        throw new NotAuthorizedException("Data has the wrong format!");
                    }

                }
                else
                {
                    throw new NotAuthorizedException("User is no project member!");
                }
            }));
        }

        [HttpGet("[controller]/vocabulary/{resourceId}/{path}")]
        public IActionResult GetVocabulary(string resourceId, string path)
        {
            return Ok(_authenticator.ValidateAndExecute((user) =>
            {
                var resource = _resourceModel.GetById(Guid.Parse(resourceId));
                if (_metadataModel.IsProjectMember(user, resource))
                {
                    var graph = _util.GetGraph(HttpUtility.UrlDecode(path));

                    JArray de = new JArray();
                    foreach (var kv in _util.GetVocabularyLabels(graph, "de"))
                    {
                        JObject obj = new JObject
                        {
                            ["value"] = kv.Key,
                            ["name"] = kv.Value
                        };
                        de.Add(obj);
                    }

                    JArray en = new JArray();
                    foreach(var kv in _util.GetVocabularyLabels(graph, "en"))
                    {
                        JObject obj = new JObject
                        {
                            ["value"] = kv.Key,
                            ["name"] = kv.Value
                        };
                        en.Add(obj);
                    }

                    JObject json = new JObject
                    {
                        ["de"] = de,
                        ["en"] = en
                    };

                    return json;
                }
                else
                {
                    throw new NotAuthorizedException("User is no project member!");
                }
            }));
        }

    }
}