package ua.be.wee.model;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ua.be.wee.controller.PMTrigger;
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.AutomatedStatus;
import ua.be.wee.model.nodes.FinalNode;
import ua.be.wee.model.nodes.ForkJoinNode;
import ua.be.wee.model.nodes.InvokingActivity;
import ua.be.wee.model.nodes.Node;
import ua.be.wee.model.nodes.ports.ControlInputPort;
import ua.be.wee.model.nodes.ports.ControlOutputPort;
import ua.be.wee.model.nodes.ports.DataInputPort;
import ua.be.wee.model.pm.PM;
import ua.be.wee.model.pt.EndActivityEvent;
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.repository.NodeRespository;
import ua.be.wee.model.repository.PMRepository;
import ua.be.wee.model.repository.PTRepository;
import ua.be.wee.model.util.AsyncHttpClientService;
import ua.be.wee.model.util.Pair;
import ua.be.wee.service.FileStorageService;

@Component
public class EnactmentController {

	@Autowired
	private PMRepository pmRepo;
	
	@Autowired
	private NodeRespository nodeRepo;
	
	@Autowired
	private PTRepository traceRepo;
	
	@Autowired
	private FileStorageService storageService;
	
	@Autowired
	private ExecutionFlowManager executionFlowManager;
	
	public List<PM> getAllPMs() {
		return pmRepo.getAllPMs();
	}

	public PM getPM(String pmid) throws Exception {
		PM pm = pmRepo.getPM(pmid);
		pm.setNodes(nodeRepo.getNodes(pm.getIri()));
		return pm;
	}

	public List<Pair<String,String>> findNextNodes(String iri, String trace, ExecutionFlowManager executionFlowManager) throws Exception {
		return pmRepo.findNextNodes(iri, trace, executionFlowManager); 
	}

	public PT createTrace(PM pm) throws Exception {
		return traceRepo.createTrace(pm);
	}
	
	public ExecutionFlowManager getExecutionFlowManager() {
	    return executionFlowManager;
	}

	public String addStartEvent(PT pt, ControlInputPort port, Activity act, List<TraceArtifact> arts, String branchId) throws Exception {
	    String iri = traceRepo.createStartEvent(pt, port, act, arts, branchId); 
	    pmRepo.checkJoinSync(port.getIri(), pt.getIri());
	    return iri;
	}
	
	public String getParentBranchId(String childBranchId) {
	    return traceRepo.getParentBranchId(childBranchId);
	}

	public void addEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p, String branchId) throws Exception {
	    traceRepo.createEndEvent(pt, arts, p, branchId);
	}
	
	public List<PMTrigger> getAvailableActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
	    List<Event> events = getEvents(pt.getIri(), branchId);
	    
	    Set<String> startedActivities = events.stream()
	        .filter(e -> e instanceof StartActivityEvent)
	        .map(e -> ((StartActivityEvent) e).getRelatesTo().getActivity().getIri())
	        .collect(Collectors.toSet());

	    Set<String> frontierNodes = new HashSet<>();

	    if (events.isEmpty() || (events.size() == 1 && events.get(0) instanceof StartTraceEvent)) {
	        frontierNodes.add(pm.getInitial().getIri());
	    } else {
	        for (Event e : events) {
	            if (e instanceof EndActivityEvent) {
	                frontierNodes.add(((EndActivityEvent) e).getRelatesTo().getIri());
	            }
	        }
	    }

	    for (Node node : pm.getNodes()) {
	        if (node instanceof ForkJoinNode) {
	            if (((ForkJoinNode) node).getNextNodes().size() > 1) {
	                 frontierNodes.add(node.getIri());
	            }
	        }
	    }
	    
	    Map<String, String> potentialNextMap = new LinkedHashMap<>();
	    for (String nodeIri : frontierNodes) {
	        for (Pair<String, String> pair : findNextNodes(nodeIri, pt.getIri(), this.executionFlowManager)) {
	            potentialNextMap.put(pair.getFst(), pair.getSnd());
	        }
	    }
	    
	    List<Pair<String, String>> filteredIris = new ArrayList<>();
	    for (Map.Entry<String, String> entry : potentialNextMap.entrySet()) {
	        String activityIri = entry.getValue();
	        if (activityIri == null || !startedActivities.contains(activityIri)) {
	            filteredIris.add(new Pair<>(entry.getKey(), activityIri));
	        }
	    }
	    
	    return generatePMTriggers(pm, filteredIris, branchId, invokingActivityMap.get(branchId));
	}

	private List<PMTrigger> generatePMTriggers(PM pm, List<Pair<String, String>> iris, String branchId, String invokingActivityName) {
	    List<PMTrigger> triggers = new ArrayList<>();
	    for (Pair<String, String> pair : iris) {
	        PMTrigger trigger = new PMTrigger();
	        String nodeIri = (pair.getSnd() != null) ? pair.getSnd() : pair.getFst();
	        Node node = pm.getNode(nodeIri);

	        if (node == null) continue;

	        if (pair.getSnd() != null && node instanceof Activity) {
	            Activity activity = (Activity) node;
	            ControlInputPort port = activity.getCtrlInPorts().stream()
	                    .filter(ctr -> ctr.getIri().equals(pair.getFst()))
	                    .findFirst().orElse(null);
	            if (port != null) {
	                trigger.setPort(port);
	                trigger.setBranchId(branchId);
	                trigger.setInvokingActivityName(invokingActivityName);
	                triggers.add(trigger);
	            }
	        } else if (node instanceof FinalNode) {
	            trigger.setNode((FinalNode) node);
	            trigger.setBranchId(branchId);
	            triggers.add(trigger);
	        }
	    }
	    return triggers;
	}
	
	private PMTrigger generatePMTrigger(PM pm, ControlInputPort port, String branchId, String invokingActivityName) {
	    if (port == null) {
	        return null;
	    }
	    PMTrigger trigger = new PMTrigger();
	    trigger.setPort(port);
	    trigger.setBranchId(branchId);
	    trigger.setInvokingActivityName(invokingActivityName);
	    return trigger;
	}

	
	public List<PMTrigger> getRunningActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
	    List<Event> events = getEvents(pt.getIri(), branchId);
	    List<PMTrigger> runningActs = new ArrayList<>();

	    for (Event event : events) {
	        if (event instanceof StartActivityEvent) {
	            StartActivityEvent startEvent = (StartActivityEvent) event;
	            Activity activity = startEvent.getRelatesTo().getActivity();

	            if (activity instanceof InvokingActivity) {
	                continue;
	            }

	            boolean ended = events.stream()
	                    .filter(e -> e instanceof EndActivityEvent)
	                    .map(e -> (EndActivityEvent) e)
	                    .anyMatch(e -> e.getRelatesTo().getActivity().getIri().equals(activity.getIri()));

	            if (!ended) {
	                boolean tokenExists = this.executionFlowManager.getActiveTokens(pt.getIri())
	                        .stream()
	                        .anyMatch(t -> t.getCurrentNode().getIri().equals(activity.getIri()));

	                if (!tokenExists) {
	                    System.out.println("[getRunningActivities] Token created for: " + activity.getIri() + " on branch: " + branchId);
	                    this.executionFlowManager.createExecutionFlow(pt, activity, branchId, invokingActivityMap.get(branchId));
	                }

	                PMTrigger trigger = generatePMTrigger(pm, startEvent.getRelatesTo(), branchId, invokingActivityMap.get(branchId));
	                if (trigger != null) {
	                    runningActs.add(trigger);
	                }
	            }
	        }
	    }
	    System.out.println("[getRunningActivities] Returning " + runningActs.size() + " triggers for branch: " + branchId);
	    return runningActs;
	}

	public Event addEndTraceEvent(String iri, String previous, String pmIRI) throws Exception {
		return traceRepo.createEndTraceEvent(iri,previous, pmIRI);
		
	}
	
	public String generateBranchId(String traceIri, String parentBranch) {
	    return traceRepo.generateBranchId(traceIri, parentBranch);
	}
	
	public List<Event> getEvents(String traceIri, String branchId) throws Exception {
	    return traceRepo.getEvents(traceIri, branchId);
	}
	
	public String getParentTraceIri(String traceIri) {
	    return nodeRepo.getParentTraceIri(traceIri);
	}

	public void updatePT(PT pt) throws Exception {
		traceRepo.updatePT(pt);
	}
	
	public List<Event> getAllEventsForTrace(String traceIri) throws Exception {
		return traceRepo.getAllEventsForTrace(traceIri);
	}
	
	public String getPmIriForTrace(String traceIri) {
		return traceRepo.getPmIriForTrace(traceIri);
	}
	
	public List<StartTraceEvent> getActiveTraces(String pmiri) throws Exception {
	    return traceRepo.getActiveTraces(pmiri);
	}
	
	public String createStartTraceEvent(PT parentPt, PM pm, String eventIri, String parentTraceIri) throws Exception {
	    return traceRepo.createStartTraceEvent(parentPt, pm, eventIri, parentTraceIri);
	}

	public void callAutomatedActivity(PT pt, AutomatedActivity aut, ControlInputPort port, List<TraceArtifact> arts, String branchId) throws JSONException, IOException {
	    Map<String,Pair<String,String>> params = createParamsMap(aut, arts);
	    AsyncHttpClientService.asyncHTTPClient(pt, aut, port, params, this, branchId); 
	}
	
	public PM getPMbyInvokedAct(String activityIri) throws Exception {

	    Activity activity = nodeRepo.getActivityByIri(activityIri);
	    if (activity == null) {
	        throw new IllegalArgumentException("Atividade com o IRI fornecido não encontrada.");
	    }

	    String pmIri = nodeRepo.getPMIRIByInvokedActivity(activityIri);
	    PM pm = pmRepo.getPM(pmIri);

	    if (pm == null) {
	        throw new IllegalArgumentException("PM associado à atividade não encontrado.");
	    }

	    return pm;
	}


	private Map<String,Pair<String,String>> createParamsMap(AutomatedActivity aut, List<TraceArtifact> arts) {
		Map<String,Pair<String,String>> params = new HashMap<String, Pair<String,String>>();
		List<DataInputPort> datalInPorts = aut.getDatalInPorts();
		
		for (DataInputPort dataInputPort : datalInPorts) {
			for (TraceArtifact traceArtifact : arts) {
				Artifact relatesTo = traceArtifact.getRelatesTo();
				if (dataInputPort.getArtifact().getIri().equals(relatesTo.getIri())) {
					traceArtifact.getLocation();
					//TODO Instead of this, send both the inline and url to the services
					if (traceArtifact.getFileExtension().equals("txt") || 
							traceArtifact.getFileExtension().equals("csv") ||
							traceArtifact.getFileExtension().equals("xopp")||
							traceArtifact.getFileExtension().equals("drawio")) {
						String content = storageService.load(traceArtifact.getLocation());
						params.put(dataInputPort.getName(), new Pair<String, String>(traceArtifact.getFileExtension(), content));
					} else {
						String content = storageService.getURL(traceArtifact.getLocation());
						params.put(dataInputPort.getName(), new Pair<String, String>(traceArtifact.getFileExtension(), content));
					}
					
				}
			}
		}
		return params;
	}
	
	public List<Map<String, String>> getActiveBranches(PT pt) {
	    List<Map<String, String>> activeBranches = new ArrayList<>();

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

	    for (Token token : tokens) {
	        Map<String, String> branchInfo = new HashMap<>();

	        branchInfo.put("branchId", token.getBranchId());

	        String invokingActivityName = token.getInvokingActivityName();
	        branchInfo.put("invokingActivityName", invokingActivityName != null ? invokingActivityName : "Main");

	        PM pm = token.getProcessModel();
	        String pmName = (pm != null) ? pm.getName() : "Unknown";
	        branchInfo.put("pmName", pmName);

	        activeBranches.add(branchInfo);
	    }

	    return activeBranches;
	}

	public void uploadArtifact(InputStream is, String filename) throws IOException {
		storageService.save(is,filename);	
	}

	public List<AutomatedActivity> getAutomatedActivities(PT trace) {
		trace.getPmEnacted();
		List<AutomatedActivity> result = new ArrayList<AutomatedActivity>();
		List<Event> events = trace.getEvents();
		for (Event event : events) {
			if (event instanceof StartActivityEvent) {
				Activity activity = ((StartActivityEvent) event).getRelatesTo().getActivity();
				if (activity instanceof AutomatedActivity) {
					boolean found = false;
					for (Event event2 : events) {
						if (event2 instanceof EndActivityEvent) {
							if (checkMatchingActivityEvents((StartActivityEvent)event, (EndActivityEvent)event2)) {
								AutomatedActivity aut =(AutomatedActivity)activity;
								aut.setAutomatedStatus(AutomatedStatus.COMPLETED);
								result.add(aut);
								found = true;
								break;
							}
							
						}
					}
					if (!found) {
						AutomatedActivity aut =(AutomatedActivity)activity;
						aut.setAutomatedStatus(AutomatedStatus.IN_PROGRESS);
						result.add(aut);	
					}
				}
			} 
		}
		return result;
	}


	public boolean checkMatchingActivityEvents(StartActivityEvent ev1, EndActivityEvent ev2) {
		String ir = ev1.getIri();
		String ir2= ev2.getIri();
		String[] split = ir.split("#");
		String[] split2 = ir2.split("#");
		String sub1 = split[1].substring(split[1].indexOf('_'));
		String sub2 = split2[1].substring(split2[1].indexOf('_'));
		return split[0].equals(split2[0]) && sub1.equals(sub2);
	}


	
}