package ua.be.wee.model.repository;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.RDFNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import ua.be.wee.model.ExecutionFlowManager;
import ua.be.wee.model.Token;
import ua.be.wee.model.nodes.Activity;
import ua.be.wee.model.nodes.Artifact;
import ua.be.wee.model.nodes.AutomatedActivity;
import ua.be.wee.model.nodes.InvokingActivity;
import ua.be.wee.model.nodes.ports.ControlInputPort;
import ua.be.wee.model.nodes.ports.ControlOutputPort;
import ua.be.wee.model.pm.PM;
import ua.be.wee.model.pt.EndActivityEvent;
import ua.be.wee.model.pt.EndTraceEvent;
import ua.be.wee.model.pt.Event;
import ua.be.wee.model.pt.PT;
import ua.be.wee.model.pt.StartActivityEvent;
import ua.be.wee.model.pt.StartTraceEvent;
import ua.be.wee.model.pt.TraceArtifact;
import ua.be.wee.model.util.DateTimeConverter;
import ua.be.wee.model.util.Pair;

@Repository
public class PTRepository {

	@Autowired
	private PMRepository pmRepo;

	@Autowired
	private NodeRespository nodeRepo;
	
	@Autowired
	private ExecutionFlowManager executionFlowManager;


	public PT createTrace(PM pm) throws Exception {
		int index = getNextIndex(pm);
		String traceiri = pm.getIri().split("#")[0] + "/traces#pt_" + index;
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "INSERT {\n" + "GRAPH <" + PT.TRACE_GRAPH_IRI
				+ "> {" + "	<" + traceiri
				+ "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> ;\n"
				+ "    	tr:relatesTo <" + pm.getIri() + "> ;\n" + " 	tr:hasTimestamp ?now ; \n"
				+ "     owl:sameAs  <" + traceiri + "> .\n" + "}" + "} WHERE {" + "	SELECT ?now\n" + "    WHERE\n"
				+ "      {\n" + "      BIND(now() AS ?now) .\n" + "      }" + "}";
		if (FusekiWrapper.getInstance().updateQuery(query)) {
			PT pt = new PT();
			pt.setPmEnacted(pm);
			pt.setIri(traceiri);

			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT ?ts  WHERE {\n"
					+ "	?pt a tr:StartTraceEvent ;\n" + "  		tr:hasTimestamp ?ts ;\n" + "  		owl:sameAs <"
					+ traceiri + "> .\n" + "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");

				StartTraceEvent ev = new StartTraceEvent();
				ev.setIri(traceiri);
				ev.setPmEnacted(pm);
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
				List<Event> list = new ArrayList<Event>();
				list.add(ev);
				pt.setEvents(list);
			}
			return pt;
		} else {
			throw new Exception("Error inserting data.");
		}

	}

	private int getNextIndex(PM pm) {
		int index = 0;
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?pt  WHERE {\n" + "	?pt a tr:StartTraceEvent ;\n" + "  		tr:relatesTo <" + pm.getIri()
				+ "> .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			results.next();
			index++;
		}
		return index;
	}

	public String createStartTraceEvent(PT parentPt, PM pm, String eventIri, String parentTraceIri) throws Exception {
	    int index = parentPt.getEvents().size();
	    
		String[] iriParts = parentPt.getIri().split("#");
		String baseIri = iriParts[0];
		String fragment = iriParts.length > 1 ? iriParts[1] : "";
		String newTraceIri = baseIri + "#" + fragment + "_sub_" + index;
	    
	    String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
	            + "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	            + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
	            + "INSERT {\n"
	            + "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
	            + "    <" + newTraceIri + "> rdf:type tr:StartTraceEvent, tr:Event, owl:Thing ;\n"
	            + "    tr:hasTimestamp ?now ; \n"
	            + "    tr:isPrecededBy <" + eventIri + "> ;\n" 
	            + "    tr:relatesTo <" + pm.getIri() + "> ;\n" 
	            + "    owl:sameAs <" + newTraceIri + "> .\n";

	    if (parentTraceIri != null) {
	        query += "    <" + newTraceIri + "> tr:invokedByTrace <" + parentTraceIri + "> .\n";
	    }

	    query += "  }\n"
	            + "} WHERE {\n"
	            + "  SELECT ?now WHERE { BIND(now() AS ?now) }\n"
	            + "}";

	    StartTraceEvent ev = new StartTraceEvent();
	    if (FusekiWrapper.getInstance().updateQuery(query)) {
	        ev.setIri(newTraceIri);
	        ev.setPmEnacted(pm); 
	        parentPt.addEvent(ev);

	        query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	                + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
	                + "SELECT ?ts WHERE {\n"
	                + "  <" + newTraceIri + "> tr:hasTimestamp ?ts ;\n"
	                + "  owl:sameAs <" + newTraceIri + "> .\n"
	                + "}";

	        ResultSet results = FusekiWrapper.getInstance().execQuery(query);
	        if (results.hasNext()) {
	            QuerySolution next = results.next();
	            RDFNode rdfNode = next.get("?ts");
	            ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
	        }
	    } else {
	        throw new Exception("Error inserting data.");
	    }

	    updatePT(parentPt);
	    return ev.getIri();
	}
	
	public String getParentBranchId(String childBranchId) {
	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
	                   "SELECT ?parent WHERE { <" + childBranchId + "> tr:parentBranch ?parent . }";
	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
	    if (rs.hasNext()) {
	        return rs.nextSolution().getResource("?parent").getURI();
	    }
	    return null; // Return null if doesn't have a parent 
	}

	public String generateBranchId(String traceIri, String parentBranchId) {
	    String base = parentBranchId != null ? parentBranchId : traceIri + "/branches";
	    String candidate = base + "/" + UUID.randomUUID().toString().substring(0, 8);

	    String query = "ASK { <" + candidate + "> ?p ?o }";
	    boolean exists = FusekiWrapper.getInstance().execAsk(query);

	    while (exists) {
	        candidate = base + "/" + UUID.randomUUID().toString().substring(0, 8);
	        exists = FusekiWrapper.getInstance().execAsk(query);
	    }

	    if (parentBranchId != null) {
	        String insert = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	                + "INSERT DATA { <" + candidate + "> tr:parentBranch <" + parentBranchId + "> . }";
	        FusekiWrapper.getInstance().updateQuery(insert);
	    }

	    return candidate;
	}


	public String createStartEvent(
	        PT pt,
	        ControlInputPort port,
	        Activity act,
	        List<TraceArtifact> arts,
	        String parentBranchId
	) throws Exception {

	    boolean isHierarchical = act instanceof InvokingActivity;
	    String branchId = isHierarchical
	        ? (parentBranchId != null ? parentBranchId : generateBranchId(pt.getIri(), null))
	        : parentBranchId;

	    System.out.println("[createStartEvent] Checking if activity already started: " + act.getIri() + " in branch: " + branchId);
		
	    if (isActivityAlreadyStarted(pt, act.getIri(), branchId)) {
	        System.err.println("[createStartEvent] Tentativa de iniciar atividade duplicada bloqueada: " + act.getName());
	        return null; 
	    }

	    String preiri;
	    String predecessorQuery = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#> SELECT ?predecessor WHERE { ?predecessor pm:ctrlTo <" + port.getIri() + "> . }";
	    ResultSet rsPredecessor = FusekiWrapper.getInstance().execQuery(predecessorQuery);

	    if (rsPredecessor.hasNext()) {
	        preiri = rsPredecessor.nextSolution().get("?predecessor").toString();
	        System.out.println("[createStartEvent] Predecessor real encontrado no grafo: " + preiri);
	    } else {
	        preiri = pt.getIri();
	        System.out.println("[createStartEvent] Nenhum predecessor encontrado, usando o trace como início: " + preiri);
	    }
	    
	    int index = (int) pt.getEvents().stream()
	            .filter(e -> e instanceof StartActivityEvent)
	            .count();

		String[] iriParts = pt.getIri().split("#");
		String baseIri = iriParts[0];
		String fragment = iriParts.length > 1 ? iriParts[1] : "";
		String iri = baseIri.replace("/traces", "/traces/" + fragment) + "#start_activity" + index;
		String endiri = baseIri.replace("/traces", "/traces/" + fragment) + "#end_activity" + index;
	    
		String portiri = port.getIri();

	    System.out.println("[LOG] [createStartEvent] Creating StartActivityEvent with IRI: " + iri);

	    StringBuilder query = new StringBuilder();
	    query.append("PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n")
	         .append("PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n")
	         .append("PREFIX owl: <http://www.w3.org/2002/07/owl#>\n")
	         .append("PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n")
	         .append("PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n")
	         .append("INSERT { GRAPH <").append(PT.TRACE_GRAPH_IRI).append("> {\n")
	         .append("<").append(iri).append("> rdf:type tr:StartActivityEvent, tr:element, tr:Event, owl:Thing, base:element ;\n")
	         .append(" base:acyclicForward <").append(endiri).append("> ;\n")
	         .append(" tr:hasTimestamp ?now ;\n")
	         .append(" tr:isPrecededBy <").append(preiri).append("> ;\n")
	         .append(" tr:relatesTo <").append(portiri).append("> ;\n")
	         .append(" owl:sameAs <").append(iri).append("> .\n")
	         .append("<").append(preiri).append("> tr:isFollowedBy <").append(iri).append("> .\n");

	    if (branchId != null) {
	        query.append("<").append(iri).append("> tr:belongsToBranch <").append(branchId).append("> .\n");
	    }

	    if (arts != null) {
	        for (TraceArtifact artifact : arts) {
	            query.append("<").append(iri).append("> tr:consumes <").append(artifact.getIri()).append("> .\n");
	        }
	    }

	    query.append("} } WHERE { SELECT (NOW() AS ?now) WHERE {} }");

	    boolean success = FusekiWrapper.getInstance().updateQuery(query.toString());
	    if (!success) {
	        System.err.println("[ERROR] [createStartEvent] Failed to insert event " + iri);
	        throw new Exception("Failed to insert event " + iri);
	    }

	    StartActivityEvent ev = new StartActivityEvent();
	    ev.setBranchId(branchId);
	    ev.setIri(iri);
	    ev.setRelatesTo(port);
	    pt.addEvent(ev);
	    System.out.println("[createStartEvent] StartActivityEvent added in memory for activity: " + act.getIri());

	    String tsQuery = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	            + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
	            + "SELECT ?ts WHERE {\n"
	            + "  <" + iri + "> tr:hasTimestamp ?ts ;\n"
	            + "      owl:sameAs <" + iri + "> .\n"
	            + "}";

	    ResultSet results = FusekiWrapper.getInstance().execQuery(tsQuery);
	    if (results.hasNext()) {
	        ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(results.next().get("?ts").toString()));
	    }
	    if (arts != null) {
	        for (TraceArtifact art : arts) {
	            String artQuery = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	                    + "INSERT DATA { GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
	                    + "<" + art.getIri() + "> pt:isConsumedBy <" + iri + "> .\n"
	                    + "} }";
	            FusekiWrapper.getInstance().updateQuery(artQuery);
	            art.setConsumedBy(ev);
	            ev.addTraceArtifact(art);
	        }
	    }

	    updatePT(pt);
	    return iri;
	}
	
	public void updatePT(PT pt) throws Exception {
		String transitive = "";
		for (Event ev : pt.getEvents()) {
			if (ev instanceof StartTraceEvent) {
				continue;
			} else {
				transitive += "<" + ev.getIri() + ">,";
			}
		}
		transitive = transitive.substring(0, transitive.length() - 1);

		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n" + "DELETE { " + "GRAPH <"
				+ PT.TRACE_GRAPH_IRI + "> {" + "?pt ?p ?v . }\n" + "}" + "WHERE { \n" + "  ?pt a tr:StartTraceEvent .\n"
				+ "  ?pt owl:sameAs <" + pt.getIri() + "> .\n" + "  ?pt ?p ?v .\n" + "};\n" + "INSERT DATA {\n"
				+ "GRAPH <" + PT.TRACE_GRAPH_IRI + "> {" + "	<" + pt.getIri()
				+ "> rdf:type    <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> ;\n"
				+ "    base:acyclicForward <" + pt.getEvents().get(1).getIri() + "> ;\n" + "    base:transitiveForward "
				+ transitive + " ;\n" + "    tr:isFollowedBy <" + pt.getEvents().get(1).getIri() + "> ;\n"
				+ "    tr:hasTimestamp "
				+ DateTimeConverter.convertTimestampToSPARQLdateTime(pt.getEvents().get(0).getTimestamp()) + " ; \n "
				+ "    tr:relatesTo <" + pt.getPmEnacted().getIri() + "> ;\n" + "    owl:sameAs <" + pt.getIri()
				+ "> .\n" + "}" + "};";
		if (!FusekiWrapper.getInstance().updateQuery(query)) {
			throw new Exception("Error inserting data.");
		}

	}
	
	public String getPmIriForTrace(String traceIri) {
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?pm WHERE {\n"
				+ "  <" + traceIri + "> a tr:StartTraceEvent ;\n"
				+ "      tr:relatesTo ?pm .\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			return rs.nextSolution().getResource("?pm").getURI();
		}
		return null;
	}
	
	public List<Event> getAllEventsForTrace(String topLevelTraceIri) throws Exception {
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT DISTINCT ?event ?type ?timestamp ?port ?branchId WHERE {\n"
				+ "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
				+ "    ?trace a tr:StartTraceEvent .\n"
				+ "    ?trace (tr:invokedByTrace)* <" + topLevelTraceIri + "> .\n"
				+ "    BIND(REPLACE(STR(?trace), '#', '/') AS ?traceEventBase)\n"
				+ "    ?event a ?type .\n"
				+ "    FILTER(STRSTARTS(STR(?event), ?traceEventBase) || ?event = ?trace)\n"
				+ "    ?event tr:hasTimestamp ?timestamp .\n"
				+ "    FILTER(?type IN (tr:StartTraceEvent, tr:EndTraceEvent, tr:StartActivityEvent, tr:EndActivityEvent))\n"
				+ "    OPTIONAL { ?event tr:relatesTo ?port . }\n"
				+ "    OPTIONAL { ?event tr:belongsToBranch ?branchId . }\n"
				+ "  }\n"
				+ "} ORDER BY ?timestamp";

		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		List<Event> events = new ArrayList<>();
		List<String> eventIris = new ArrayList<>(); 

		while (rs.hasNext()) {
			QuerySolution sol = rs.next();
			String eventIRI = sol.get("?event").toString();

			if (eventIris.contains(eventIRI)) {
				continue;
			}
			eventIris.add(eventIRI);

			String type = sol.get("?type").toString();
			String timestamp = sol.get("?timestamp").toString();
			RDFNode portNode = sol.get("?port");
			RDFNode branchNode = sol.get("?branchId");
			String branchId = (branchNode != null) ? branchNode.toString() : null;

			Event ev = null;
			if (type.endsWith("StartTraceEvent")) {
				ev = new StartTraceEvent();
			} else if (type.endsWith("EndTraceEvent")) {
				ev = new EndTraceEvent();
			} else if (type.endsWith("StartActivityEvent")) {
				StartActivityEvent startEv = new StartActivityEvent();
				startEv.setBranchId(branchId);
				if (portNode != null) {
					ControlInputPort port = nodeRepo.getControlInputPort(portNode.toString());
					startEv.setRelatesTo(port);
				}
				ev = startEv;
			} else if (type.endsWith("EndActivityEvent")) {
				EndActivityEvent endEv = new EndActivityEvent();
				endEv.setBranchId(branchId);
				if (portNode != null) {
					ControlOutputPort port = nodeRepo.getControlOutputPort(portNode.toString());
					endEv.setRelatesTo(port);
				}
				ev = endEv;
			}

			if (ev != null) {
				ev.setIri(eventIRI);
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(timestamp));
				events.add(ev);
			}
		}
		return events;
	}

	public void createEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p, String branchId) throws Exception {
	    System.out.println("initializing createEndEvent");
	    System.out.println("Branch ID: " + branchId);
	    System.out.println("Activity IRI: " + p.getActivity().getIri());
	    System.out.println("Port IRI: " + p.getIri());

	    List<Event> events = pt.getEvents();
	    Event source = null;

	    for (Event aux : events) {
	        if (aux instanceof StartActivityEvent) {
	            StartActivityEvent startEv = (StartActivityEvent) aux;
	            boolean sameBranch = (branchId == null && startEv.getBranchId() == null)
	                    || (branchId != null && branchId.equals(startEv.getBranchId()));
	            boolean sameActivity = p.getActivity().getIri().equals(
	                    startEv.getRelatesTo().getActivity().getIri());

	            if (sameBranch && sameActivity) {
	                source = startEv;
	                System.out.println("StartActivityEvent found in memory: " + startEv.getIri());
	                break;
	            }
	        }
	    }

	    if (source == null) {
	        System.out.println("StartActivityEvent isn't in memory. Consulting RDF");

	        String activityIRI = p.getActivity().getIri();
	        String pmGraphIRI = activityIRI.split("#")[0];

	        String queryFindStartEvent =
	                "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
	                        "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" +
	                        "SELECT ?startEvent WHERE {\n" +
	                        "  ?startEvent a tr:StartActivityEvent ;\n" +
	                        "    tr:belongsToBranch <" + branchId + "> ;\n" +
	                        "    tr:relatesTo ?port .\n" +
	                        "  GRAPH <" + pmGraphIRI + "> {\n" +
	                        "    ?port pm:ofActivity <" + activityIRI + "> .\n" +
	                        "  }\n" +
	                        "}";

	        System.out.println("[LOG] Query SPARQL:\n" + queryFindStartEvent);

	        ResultSet rs = FusekiWrapper.getInstance().execQuery(queryFindStartEvent);
	        if (rs.hasNext()) {
	            String startEventIri = rs.next().getResource("?startEvent").getURI();
	            System.out.println("[LOG] StartActivityEvent found on RDF: " + startEventIri);

	            for (Event aux : pt.getEvents()) {
	                if (aux.getIri().equals(startEventIri)) {
	                    source = aux;
	                    break;
	                }
	            }
	        }
	    }

	    if (source == null) {
	        System.out.println(" StartActivityEvent not found in memory or RDF");
	        throw new Exception("StartActivityEvent not found in branch " + branchId);
	    }

	    String preiri = pt.getLastEvent() != null ? pt.getLastEvent().getIri() : pt.getIri();
	    String iri = source.getIri();
	    String indexStr = iri.replaceAll("\\D+", "");
	    int index = Integer.parseInt(indexStr);

		String[] iriParts = pt.getIri().split("#");
		String baseIri = iriParts[0];
		String fragment = iriParts.length > 1 ? iriParts[1] : "";
		String endiri = baseIri.replace("/traces", "/traces/" + fragment) + "#end_activity" + index;

	    List<String> artifactsIRI = new ArrayList<>();
	    for (TraceArtifact art : arts) {
	        artifactsIRI.add(createTraceArtifact(endiri, preiri, art, pt));
	    }

	    StringBuilder queryBuilder = new StringBuilder();
	    queryBuilder.append("PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n")
	            .append("PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n")
	            .append("PREFIX owl: <http://www.w3.org/2002/07/owl#>\n")
	            .append("PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n")
	            .append("INSERT {\n")
	            .append("GRAPH <").append(PT.TRACE_GRAPH_IRI).append("> {\n")
	            .append("  <").append(endiri).append("> rdf:type tr:EndActivityEvent, tr:element, tr:Event, owl:Thing, base:element ;\n")
	            .append("    tr:isPrecededBy <").append(preiri).append("> ;\n")
	            .append("    tr:relatesTo <").append(p.getIri()).append("> ;\n")
	            .append("    tr:hasTimestamp ?now ;\n")
	            .append("    tr:belongsToBranch <").append(branchId).append("> ;\n")
	            .append("    owl:sameAs <").append(endiri).append("> .\n"); // Removido ponto e vírgula extra

	    for (String iris : artifactsIRI) {
	        queryBuilder.append("    <").append(endiri).append("> tr:produces <").append(iris).append("> .\n");
	    }

	    queryBuilder.append("  <").append(preiri).append("> tr:isFollowedBy <").append(endiri).append("> .\n")
	            .append("} }\n") 
	            .append("WHERE {\n")
	            .append("  SELECT ?now WHERE { BIND(now() AS ?now) }\n")
	            .append("}");

	    String query = queryBuilder.toString();

	    if (FusekiWrapper.getInstance().updateQuery(query)) {
	        EndActivityEvent ev = new EndActivityEvent();
	        ev.setIri(endiri);
	        ev.setRelatesTo(p);
	        ev.setBranchId(branchId);

	        String tsQuery =
	                "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
	                        "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" +
	                        "SELECT ?ts WHERE {\n" +
	                        "  <" + endiri + "> tr:hasTimestamp ?ts ;\n" +
	                        "    owl:sameAs <" + endiri + "> .\n" +
	                        "}";

	        ResultSet results = FusekiWrapper.getInstance().execQuery(tsQuery);
	        if (results.hasNext()) {
	            ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(
	                    results.next().get("?ts").toString()
	            ));
	        }

	        for (TraceArtifact art : arts) {
	            art.setProducedBy(ev);
	            ev.addTraceArtifact(art);
	        }

	        pt.addEvent(ev);
	        List<Token> tokens = executionFlowManager.getActiveTokens(pt.getIri());

	        if (tokens != null) {
	            for (Token token : new ArrayList<>(tokens)) {
	                if (token.getCurrentNode() != null &&
	                    token.getCurrentNode().getIri().equals(p.getActivity().getIri()) &&
	                    token.getBranchId().equals(branchId)) {

	                    System.out.println("[DEBUG] [createEndEvent] Removing token for activity: " + token.getCurrentNode().getIri());
	                    executionFlowManager.removeToken(pt.getIri(), token.getId());
	                    break;
	                }
	            }
	        }
			
	        updatePT(pt);
	    } else {
	        throw new Exception("fail to insert EndActivityEvent on branch " + branchId);
	    }
	}



	private String createTraceArtifact(String endEvIRI, String startEvIRI, TraceArtifact art, PT pt) throws Exception {

		TraceArtifact latest = getLastestVersion(startEvIRI, art);
		if (latest != null) {
			String newTag = (Integer.parseInt(latest.getTag().substring(1)) + 1) + "";
			art.setTag("v" + newTag);
		} else {
			art.setTag("v1");
		}
		art.setGUID(art.getRelatesTo().getName() + "-" + art.getTag());
		art.setIri(pt.getIri().replace('#', '/') + "#" + art.getGUID());

		String query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n" + "INSERT { \n" + "GRAPH <"
				+ PT.TRACE_GRAPH_IRI + "> {" + " <" + art.getIri()
				+ "> rdf:type base:Versionable , pt:Artifact , owl:Thing , pt:element , base:ImmutableThing ;\n"
				+ "        pt:hasLocation \"" + art.getLocation() + "\" ;\n" + "        pt:isProducedBy <" + endEvIRI
				+ "> ;\n" + "        pt:relatesTo <" + art.getRelatesTo().getIri() + "> ;\n"
				+ "	       pt:addedAt ?now ; \n " + "        owl:sameAs  <" + art.getIri() + "> ;\n";
		if (latest != null) {
			query += "        base:nextVersionOf <" + latest.getIri() + "> ;\n" + "        base:hasGUID \""
					+ art.getGUID() + "\" ;\n" + "        base:hasTag \"" + art.getTag() + "\" .\n" + "  <"
					+ latest.getIri() + "> base:previousVersionOf <" + art.getIri() + "> .\n";
		} else {
			query += "        base:hasGUID \"" + art.getGUID() + "\" ;\n" + "        base:hasTag \"" + art.getTag()
					+ "\" .\n";
		}
		query += "}" + "} WHERE {" + "	SELECT ?now\n" + "    WHERE\n" + "      {\n" + "      BIND(now() AS ?now) .\n"
				+ "      }" + "}";

		if (FusekiWrapper.getInstance().updateQuery(query)) {

			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:addedAt ?ts ;\n" + "  		owl:sameAs <" + art.getIri() + "> .\n" + "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				art.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}

			if (latest != null) {
				List<TraceArtifact> inputs = pt.getInputs();
				boolean found = false;
				if (inputs != null) {
					for (TraceArtifact traceArtifact : inputs) {
						if (traceArtifact.getIri().equals(latest.getIri())) {
							traceArtifact.setNextVersion(art);
							found = true;
							break;
						}
					}
				}

				if (!found) {
					List<Event> events = pt.getEvents();
					for (Event event : events) {
						if (event instanceof StartActivityEvent
								&& ((StartActivityEvent) event).getArtifact(latest.getIri()) != null) {
							((StartActivityEvent) event).getArtifact(latest.getIri()).setNextVersion(art);
							break;
						} else if (event instanceof EndActivityEvent
								&& ((EndActivityEvent) event).getArtifact(latest.getIri()) != null) {
							((EndActivityEvent) event).getArtifact(latest.getIri()).setNextVersion(art);
							break;
						}
					}
				}

			}
			return art.getIri();
		} else {
			throw new Exception("Error inserting data.");
		}
	}
	
	public boolean isActivityAlreadyStarted(PT pt, String activityIri, String branchId) {
	    System.out.println("[isActivityAlreadyStarted] Checking activity " + activityIri + " within branch " + branchId);
	    
	    List<Event> branchEvents = pt.getEvents().stream()
	        .filter(e -> e.getBranchId() != null && e.getBranchId().equals(branchId))
	        .collect(Collectors.toList());

	    List<StartActivityEvent> startEventsForActivity = branchEvents.stream()
	        .filter(e -> e instanceof StartActivityEvent)
	        .map(e -> (StartActivityEvent) e)
	        .filter(se -> se.getRelatesTo().getActivity().getIri().equals(activityIri))
	        .collect(Collectors.toList());

	    if (startEventsForActivity.isEmpty()) {
	        System.out.println("[isActivityAlreadyStarted] Result: false (no start events found in this branch)");
	        return false;
	    }

	    for (StartActivityEvent startEvent : startEventsForActivity) {
	        boolean hasMatchingEndEvent = branchEvents.stream()
	            .filter(e -> e instanceof EndActivityEvent)
	            .map(e -> (EndActivityEvent) e)
	            .anyMatch(ee -> {
	                String expectedEndIri = startEvent.getIri().replace("start_activity", "end_activity");
	                return ee.getIri().equals(expectedEndIri);
	            });
	        
	        if (!hasMatchingEndEvent) {
	            System.out.println("[isActivityAlreadyStarted] Result: true (start event without matching end event found in this branch)");
	            return true;
	        }
	    }

	    System.out.println("[isActivityAlreadyStarted] Result: false (all start events have a matching end event in this branch)");
	    return false;
	}

	private TraceArtifact getLastestVersion(String startEvIRI, TraceArtifact art) {
		String query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n" + "SELECT DISTINCT ?art ?version\n"
				+ "WHERE { \n" + "  ?art a pt:Artifact .\n" + "  ?art pt:relatesTo <" + art.getRelatesTo().getIri()
				+ "> .\n" + "  ?art base:hasTag ?version .\n" + "  {\n" + "    {?art pt:isProducedBy ?endEv .\n"
				+ "    <" + startEvIRI + "> pt:isPrecededBy+ ?endEv .} \n" + "    UNION \n"
				+ "    {?art pt:isConsumedBy ?startEv .\n" + "     <" + startEvIRI + "> pt:isPrecededBy+ ?startEv .}\n"
				+ "  }  \n" + "  FILTER NOT EXISTS {\n" + "  	?art base:previousVersionOf ?newArt .\n"
				+ "    FILTER (?art != ?newArt) \n" + "  }\n" + "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode lastestIRI = next.get("?art");
			RDFNode latestVersion = next.get("?version");
			if (lastestIRI != null) {
				TraceArtifact result = new TraceArtifact();
				result.setIri(lastestIRI.toString());
				result.setTag(latestVersion.toString());
				return result;
			}
		}
		return null;
	}

	public Event createEndTraceEvent(String ptIRI, String previous, String pmIRI) throws Exception {
		String traceiri = ptIRI + "_end";

		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "INSERT {\n" + "GRAPH <" + PT.TRACE_GRAPH_IRI
				+ "> {" + "	<" + traceiri
				+ "> rdf:type <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#EndTraceEvent> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> , owl:Thing ;\n"
				+ " 	tr:isPrecededBy <" + previous + "> ;\n" + "    	tr:relatesTo <" + pmIRI + "> ;\n"
				+ "     tr:hasTimestamp ?now ; \n" + "     owl:sameAs  <" + traceiri + "> .\n" + " <" + previous
				+ "> tr:isFollowedBy <" + traceiri + "> .\n" + "}" + "} WHERE {" + "	SELECT ?now\n" + "    WHERE\n"
				+ "      {\n" + "      BIND(now() AS ?now) .\n" + "      }" + "}";

		if (FusekiWrapper.getInstance().updateQuery(query)) {
			EndTraceEvent ev = new EndTraceEvent();
			ev.setIri(traceiri);

			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:hasTimestamp ?ts ;\n" + "  		owl:sameAs <" + traceiri + "> .\n" + "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}

			ev.setPmEnacted(pmRepo.getPM(pmIRI));

			return ev;
		} else {
			throw new Exception("Error inserting data.");
		}

	}

	public List<StartTraceEvent> getStartTraceEvents() throws Exception {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT ?trace ?pm ?ts WHERE {\n"
				+ "	?trace rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> ;\n"
				+ "    	tr:relatesTo ?pm ;\n" + " 	tr:hasTimestamp ?ts ; \n" + "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode pm = next.get("?pm");
			RDFNode ts = next.get("?ts");
			StartTraceEvent pt = new StartTraceEvent();
			pt.setIri(trace.toString());
			pt.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			pt.setPmEnacted(pmRepo.getPM(pm.toString()));
			list.add(pt);
		}
		return list;
	}

	public List<Event> getEvents(String traceIri, String branchId) throws Exception {
	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
	            + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
	            + "SELECT ?event ?type ?timestamp ?port WHERE {\n"
	            + "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
	            + "    ?event a ?type ;\n"
	            + "           tr:hasTimestamp ?timestamp ;\n"
	            + "           tr:belongsToBranch <" + branchId + "> .\n"
	            + "    OPTIONAL { ?event tr:relatesTo ?port . }\n"
	            + "    FILTER(?type IN (tr:StartTraceEvent, tr:EndTraceEvent, tr:StartActivityEvent, tr:EndActivityEvent))\n"
	            + "  }\n"
	            + "}";

	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
	    List<Event> events = new ArrayList<>();

	    while (rs.hasNext()) {
	        QuerySolution sol = rs.next();
	        String eventIRI = sol.get("?event").toString();
	        String type = sol.get("?type").toString();
	        String timestamp = sol.get("?timestamp").toString();
	        RDFNode portNode = sol.get("?port");

	        Event ev = null;
	        if (type.endsWith("StartTraceEvent")) {
	            ev = new StartTraceEvent();
	        } else if (type.endsWith("EndTraceEvent")) {
	            ev = new EndTraceEvent();
	        } else if (type.endsWith("StartActivityEvent")) {
	            StartActivityEvent startEv = new StartActivityEvent();
	            startEv.setBranchId(branchId);

	            if (portNode != null) {
	                ControlInputPort port = nodeRepo.getControlInputPort(portNode.toString());
	                startEv.setRelatesTo(port);
	            }

	            ev = startEv;
	        } else if (type.endsWith("EndActivityEvent")) {
	            EndActivityEvent endEv = new EndActivityEvent();
	            endEv.setBranchId(branchId);

	            if (portNode != null) {
	                ControlOutputPort port = nodeRepo.getControlOutputPort(portNode.toString());
	                endEv.setRelatesTo(port);
	            }

	            ev = endEv;
	        }

	        ev.setIri(eventIRI);
	        ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(timestamp));

	        events.add(ev);
	    }
	    return events;
	}

	private List<TraceArtifact> getArtifacts(Event ev, PM pm, List<TraceArtifact> artifacts) {
		List<TraceArtifact> arts = new ArrayList<TraceArtifact>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?art ?ts ?guid ?location ?tag ?pmArt ?next WHERE {\n" + "	?art a tr:Artifact .\n"
				+ (ev instanceof StartActivityEvent ? "  	<" + ev.getIri() + "> tr:consumes ?art .\n"
						: "  	<" + ev.getIri() + "> tr:produces ?art .\n")
				+ "    ?art tr:addedAt ?ts .\n" + "    ?art base:hasGUID ?guid .\n"
				+ "    ?art tr:hasLocation ?location .\n" + "    ?art base:hasTag ?tag .\n"
				+ "    ?art tr:relatesTo ?pmArt .\n" + "	   OPTIONAL {\n"
				+ "    	?art base:nextVersionOf ?next \n" + "    }" + "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode art = next.get("?art");
			RDFNode guid = next.get("?guid");
			RDFNode ts = next.get("?ts");
			RDFNode location = next.get("?location");
			RDFNode tag = next.get("?tag");
			RDFNode pmArt = next.get("?pmArt");
			RDFNode nextArt = next.get("?next");
			TraceArtifact tArt = new TraceArtifact();
			tArt.setIri(art.toString());
			tArt.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			tArt.setRelatesTo((Artifact) pm.getNode(pmArt.toString()));
			tArt.setGUID(guid.toString());
			tArt.setLocation(location.toString());
			tArt.setTag(tag.toString());
			arts.add(tArt);

			if (nextArt != null && !nextArt.toString().equals("")) {
				for (TraceArtifact traceArtifact : artifacts) {
					if (traceArtifact.getIri().equals(nextArt.toString())) {
						traceArtifact.setNextVersion(tArt);
					}
				}
			}

			artifacts.add(tArt);
		}
		return arts;
	}

	public List<StartTraceEvent> getActiveTraces(String pm) {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?trace ?ts ?next WHERE {\n" + "  ?trace tr:hasTimestamp ?ts .\n" + "  ?trace tr:relatesTo <"
				+ pm + "> .\n" + "  {  \n" + "    ?trace a tr:StartTraceEvent .\n"
				+ "	?trace tr:isFollowedBy+ ?next .\n" + "    ?next a ?nexttype .\n" + "	FILTER (?trace != ?next)\n"
				+ "	FILTER NOT EXISTS {?next tr:isFollowedBy ?other  } .\n"
				+ "    FILTER (?nexttype IN (tr:StartActivityEvent, tr:EndActivityEvent)) .\n" + "  }\n" + "  UNION \n"
				+ "  {\n" + "    ?trace a tr:StartTraceEvent .\n"
				+ "    FILTER NOT EXISTS {?trace tr:isFollowedBy ?next} .\n" + "  }\n" + "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode ts = next.get("?ts");
			StartTraceEvent ev = new StartTraceEvent();
			ev.setIri(trace.toString());
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			PM pmo = new PM();
			pmo.setIri(pm);
			ev.setPmEnacted(pmo);
			list.add(ev);
		}
		return list;
	}

	public List<StartTraceEvent> getConcludedTraces(String pmiri) {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?trace ?ts ?next WHERE {\n" + "  ?trace a tr:StartTraceEvent .\n"
				+ "  ?trace tr:hasTimestamp ?ts .\n" + "  ?trace tr:relatesTo <" + pmiri + "> .\n"
				+ "  ?trace tr:isFollowedBy+ ?next .\n" + "  ?next a ?nexttype .\n" + "  FILTER (?trace != ?next)\n"
				+ "  FILTER NOT EXISTS {?next tr:isFollowedBy ?other  } .\n"
				+ "  FILTER (?nexttype IN (tr:EndTraceEvent)) .\n" + "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode ts = next.get("?ts");
			StartTraceEvent ev = new StartTraceEvent();
			ev.setIri(trace.toString());
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			PM pmo = new PM();
			pmo.setIri(pmiri);
			ev.setPmEnacted(pmo);
			list.add(ev);
		}
		return list;
	}

}