using Coscine.ApiClient;
using Coscine.ApiClient.Core.Model;
using SQL2Linked.Utils;
using VDS.RDF;

namespace SQL2Linked.Implementations;

/// <summary>
/// Class responsible for converting project data into linked data graphs.
/// It retrieves project information from the API and then transforms this data into a series of RDF graphs,
/// making use of predefined URIs and RDF constructs.
/// </summary>
public class ProjectStructuralData : StructuralData<ProjectAdminDto>
{
    public override IAsyncEnumerable<ProjectAdminDto> GetAll() => 
        PaginationHelper.GetAllAsync<ProjectAdminDtoPagedResponse, ProjectAdminDto>(
            (currentPage) => _adminApi.GetAllProjectsAsync(includeDeleted: true, pageNumber: currentPage, pageSize: 50));

    public override async IAsyncEnumerable<IGraph> ConvertToLinkedDataAsync(IAsyncEnumerable<ProjectAdminDto> entries)
    {
        var coscineHandlePrefix = UriHelper.TryCombinePath(RdfUris.HandlePrefix, _pidConfiguration.Prefix)
            ?? throw new Exception("Could not combine handle prefix with PID prefix");

        var coscineGraph = new Graph
        {
            BaseUri = RdfUris.CoscinePrefix
        };
        AssertToGraphUriNode(coscineGraph, RdfUris.CoscinePrefix, RdfUris.DcatCatalog, RdfUris.CoscineProjects);
        AssertToGraphUriNode(coscineGraph, RdfUris.CoscinePrefix, RdfUris.DcatCatalog, RdfUris.CoscineResources);

        yield return coscineGraph; // yield coscineGraph first

        var trellisGraph = PatchGraph.Empty(RdfUris.TrellisGraph);

        await foreach (var entry in entries)
        {
            var projectGraphName = UriHelper.TryCombineUri(RdfUris.CoscineProjects, entry.Id)
                ?? throw new Exception("Could not combine projects prefix with project ID");
            var projectHandleName = UriHelper.TryCombineUri(coscineHandlePrefix, entry.Id);

            var graph = new Graph
            {
                BaseUri = projectGraphName
            };

            AssertToGraphUriNode(graph, projectGraphName, RdfUris.A, RdfUris.DcatCatalogClass);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.A} {RdfUris.DcatCatalogClass}'. ");

            AssertToGraphUriNode(graph, projectGraphName, RdfUris.A, RdfUris.OrgOrganizationalCollaborationClass);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.A} {RdfUris.OrgOrganizationalCollaborationClass}'. ");

            AssertToGraphUriNode(graph, projectGraphName, RdfUris.A, RdfUris.VcardGroupClass);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.A} {RdfUris.VcardGroupClass}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsTitle, entry.Name);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsTitle} {entry.Name}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsDescription, entry.Description);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsDescription} {entry.Description}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsStartDate, entry.StartDate.ToString(), new Uri("http://www.w3.org/2001/XMLSchema#dateTime"));
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsStartDate} {entry.StartDate}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsEndDate, entry.EndDate.ToString(), new Uri("http://www.w3.org/2001/XMLSchema#dateTime"));
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsEndDate} {entry.EndDate}'. ");

            if (entry.Keywords.Count > 0)
            {
                foreach (var keyword in entry.Keywords)
                {
                    AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsSubject, keyword);
                    Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsSubject} {keyword}'. ");
                }
            }

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsAlternative, entry.DisplayName);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsAlternative} {entry.DisplayName}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsRightsHolder, entry.PrincipleInvestigators);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsRightsHolder} {entry.PrincipleInvestigators}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.SchemaFunding, entry.GrantId);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.SchemaFunding} {entry.GrantId}'. ");

            if (entry.Visibility.DisplayName.Contains("Public"))
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.CoscineTermsProjectVisibility, RdfUris.CoscineTermsVisibilityPublic);
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.CoscineTermsProjectVisibility} {RdfUris.CoscineTermsVisibilityPublic}'. ");
            }
            else if (entry.Visibility.DisplayName.Contains("Project Members"))
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.CoscineTermsProjectVisibility, RdfUris.CoscineTermsVisibilityProjectMember);
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.CoscineTermsProjectVisibility} {RdfUris.CoscineTermsVisibilityProjectMember}'. ");
            }

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.CoscineTermsProjectDeleted, entry.Deleted.ToString().ToLower(), RdfUris.XsdBoolean);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.CoscineTermsProjectDeleted} {entry.Deleted}'. ");

            AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.CoscineTermsProjectSlug, entry.Slug);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.CoscineTermsProjectSlug} {entry.Slug}'. ");

            AssertToGraphUriNode(graph, projectGraphName, RdfUris.FoafHomepage, projectHandleName);
            Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.FoafHomepage} {projectHandleName}'. ");

            foreach (var projectRole in entry.ProjectRoles)
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.VcardHasMember, UriHelper.TryCombineUri(RdfUris.CoscineUsers, projectRole.UserId));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.VcardHasMember} {UriHelper.TryCombineUri(RdfUris.CoscineUsers, projectRole.UserId)}'. ");

                var blankNode = graph.CreateBlankNode();

                AssertToGraphBlankAndUriNode(graph, blankNode, RdfUris.A, RdfUris.OrgMembershipClass);
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{blankNode} {RdfUris.A} {RdfUris.OrgMembershipClass}'. ");

                AssertToGraphBlankAndUriNode(graph, blankNode, RdfUris.OrgOrganization, projectGraphName);
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{blankNode} {RdfUris.OrgOrganization} {projectGraphName}'. ");

                AssertToGraphBlankAndUriNode(graph, blankNode, RdfUris.OrgRole, UriHelper.TryCombineUri(RdfUris.CoscineRoles, projectRole.RoleId));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{blankNode} {RdfUris.OrgRole} {UriHelper.TryCombineUri(RdfUris.CoscineRoles, projectRole.RoleId)}'. ");

                AssertToGraphBlankAndUriNode(graph, blankNode, RdfUris.OrgMember, UriHelper.TryCombineUri(RdfUris.CoscineUsers, projectRole.UserId));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{blankNode} {RdfUris.OrgMember} {UriHelper.TryCombineUri(RdfUris.CoscineUsers, projectRole.UserId)}'. ");
            }

            foreach (var projectResource in entry.ProjectResources)
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.DcatCatalog, UriHelper.TryCombineUri(RdfUris.CoscineResources, projectResource.ResourceId));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcatCatalog} {UriHelper.TryCombineUri(RdfUris.CoscineResources, projectResource.ResourceId)}'. ");
            }

            foreach (var projectInstitute in entry.Organizations)
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.OrgOrganization, new Uri(projectInstitute.Uri));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.OrgOrganization} {projectInstitute.Uri}'. ");
            }

            if (entry.Creator is not null)
            {
                AssertToGraphUriNode(graph, projectGraphName, RdfUris.DcTermsCreator, UriHelper.TryCombineUri(RdfUris.CoscineUsers, entry.Creator.Id));
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsCreator} {UriHelper.TryCombineUri(RdfUris.CoscineUsers, entry.Creator.Id)}'. ");
            }

            if (entry.CreationDate is not null && entry.CreationDate.HasValue)
            {
                AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsCreated, entry.CreationDate.Value.ToString(), RdfUris.XsdDateTime);
                Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsCreated} {entry.CreationDate}'. ");
            }

            // Add data to the trellis graph
            AssertToGraphUriNode(trellisGraph,
                    projectGraphName,
                    RdfUris.A,
                    RdfUris.LdpBasicContainerClass);
            AddModifiedDate(trellisGraph, projectGraphName);

            AssertToGraphUriNode(trellisGraph,
                    projectGraphName,
                    RdfUris.DcTermsIsPartOf,
                    RdfUris.CoscineProjectsEntity);
            AssertToGraphUriNode(trellisGraph,
                    RdfUris.CoscineProjectsEntity,
                    RdfUris.A,
                    RdfUris.LdpBasicContainerClass);
            AddModifiedDate(trellisGraph, RdfUris.CoscineProjectsEntity);

            AssertToGraphUriNode(trellisGraph,
                    RdfUris.CoscineProjectsEntity,
                    RdfUris.DcTermsIsPartOf,
                    RdfUris.CoscinePrefix);
            AssertToGraphUriNode(trellisGraph,
                    RdfUris.CoscinePrefix,
                    RdfUris.A,
                    RdfUris.LdpBasicContainerClass);
            AddModifiedDate(trellisGraph, RdfUris.CoscinePrefix);

            yield return graph;
        }

        yield return trellisGraph;
    }
}