package selection_queries

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"strings"
)

func nodeKey(termType, iri string) string {
	return termType + "\x00" + iri
}

func valuesTerm(n NodeRef) string {
	if n.TermType == "uri" {
		if n.IRI == "" {
			return ""
		}
		return "<" + n.IRI + ">"
	}
	if n.TermType == "bnode" {
		if n.IRI == "" {
			return ""
		}
		if strings.HasPrefix(n.IRI, "_:") {
			return n.IRI
		}
		return "_:" + n.IRI
	}
	return ""
}

func termKeyFromSparqlTerm(term sparqlTerm, includeBNodes bool) (string, bool) {
	if term.Type == "" || term.Value == "" {
		return "", false
	}
	if term.Type == "literal" {
		return "", false
	}
	if term.Type == "bnode" {
		if !includeBNodes {
			return "", false
		}
		return nodeKey("bnode", "_:"+term.Value), true
	}
	if term.Type == "uri" {
		return nodeKey("uri", term.Value), true
	}
	return "", false
}

func selectedNodesFromIDs(idx Index, selectedIDs []uint32, includeBNodes bool) ([]NodeRef, map[uint32]struct{}) {
	out := make([]NodeRef, 0, len(selectedIDs))
	set := make(map[uint32]struct{}, len(selectedIDs))
	for _, nid := range selectedIDs {
		n, ok := idx.IDToNode[nid]
		if !ok {
			continue
		}
		if n.TermType == "bnode" && !includeBNodes {
			continue
		}
		out = append(out, n)
		set[nid] = struct{}{}
	}
	return out, set
}

func idFromSparqlTerm(term sparqlTerm, idx Index, includeBNodes bool) (uint32, bool) {
	key, ok := termKeyFromSparqlTerm(term, includeBNodes)
	if !ok {
		return 0, false
	}
	nid, ok := idx.KeyToID[key]
	return nid, ok
}

func tripleTermFromSparqlTerm(term sparqlTerm) TripleTerm {
	return TripleTerm{
		Type:  term.Type,
		Value: term.Value,
		Lang:  term.Lang,
	}
}

func logQueryExecutionFailure(queryName string, selectedIDs []uint32, includeBNodes bool, sparql string, err error) {
	log.Printf(
		"%s: SPARQL execution failed selected_ids=%v include_bnodes=%t err=%v\nSPARQL:\n%s",
		queryName,
		selectedIDs,
		includeBNodes,
		err,
		strings.TrimSpace(sparql),
	)
}

func resultFromTripleBindings(raw []byte, idx Index, selectedSet map[uint32]struct{}, includeBNodes bool) (Result, error) {
	var res sparqlResponse
	if err := json.Unmarshal(raw, &res); err != nil {
		return Result{}, fmt.Errorf("failed to parse SPARQL JSON: %w", err)
	}

	neighborSet := make(map[uint32]struct{})
	triples := make([]Triple, 0, len(res.Results.Bindings))
	for _, b := range res.Results.Bindings {
		sTerm, okS := b["s"]
		pTerm, okP := b["p"]
		oTerm, okO := b["o"]
		if !okS || !okP || !okO {
			continue
		}

		triple := Triple{
			S: tripleTermFromSparqlTerm(sTerm),
			P: tripleTermFromSparqlTerm(pTerm),
			O: tripleTermFromSparqlTerm(oTerm),
		}

		subjID, subjOK := idFromSparqlTerm(sTerm, idx, includeBNodes)
		if subjOK {
			id := subjID
			triple.SubjectID = &id
		}
		objID, objOK := idFromSparqlTerm(oTerm, idx, includeBNodes)
		if objOK {
			id := objID
			triple.ObjectID = &id
		}
		if pTerm.Type == "uri" {
			if predID, ok := idx.PredicateIDByIRI[pTerm.Value]; ok {
				id := predID
				triple.PredicateID = &id
			}
		}

		_, subjSelected := selectedSet[subjID]
		_, objSelected := selectedSet[objID]
		if subjOK && subjSelected && objOK && !objSelected {
			neighborSet[objID] = struct{}{}
		}
		if objOK && objSelected && subjOK && !subjSelected {
			neighborSet[subjID] = struct{}{}
		}

		triples = append(triples, triple)
	}

	ids := make([]uint32, 0, len(neighborSet))
	for nid := range neighborSet {
		ids = append(ids, nid)
	}
	sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
	return Result{NeighborIDs: ids, Triples: triples}, nil
}
