package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	graphqueries "visualizador_instanciados/backend_go/graph_queries"
	selectionqueries "visualizador_instanciados/backend_go/selection_queries"
)

type APIServer struct {
	cfg       Config
	sparql    *AnzoGraphClient
	snapshots *GraphSnapshotService
}

func (s *APIServer) handler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/health", s.handleHealth)
	mux.HandleFunc("/api/stats", s.handleStats)
	mux.HandleFunc("/api/sparql", s.handleSparql)
	mux.HandleFunc("/api/graph", s.handleGraph)
	mux.HandleFunc("/api/graph_queries", s.handleGraphQueries)
	mux.HandleFunc("/api/selection_queries", s.handleSelectionQueries)
	mux.HandleFunc("/api/selection_query", s.handleSelectionQuery)
	mux.HandleFunc("/api/neighbors", s.handleNeighbors)

	return s.corsMiddleware(mux)
}

func (s *APIServer) corsMiddleware(next http.Handler) http.Handler {
	origins := s.cfg.corsOriginList()
	allowAll := len(origins) == 1 && origins[0] == "*"
	allowed := make(map[string]struct{}, len(origins))
	for _, o := range origins {
		allowed[o] = struct{}{}
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		origin := r.Header.Get("Origin")
		if origin != "" {
			if allowAll {
				w.Header().Set("Access-Control-Allow-Origin", "*")
			} else if _, ok := allowed[origin]; ok {
				w.Header().Set("Access-Control-Allow-Origin", origin)
				w.Header().Add("Vary", "Origin")
			}
			w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "*")
		}

		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}

		next.ServeHTTP(w, r)
	})
}

func (s *APIServer) handleHealth(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	writeJSON(w, http.StatusOK, HealthResponse{Status: "ok"})
}

func (s *APIServer) handleStats(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	ctx := r.Context()
	snap, err := s.snapshots.Get(ctx, s.cfg.DefaultNodeLimit, s.cfg.DefaultEdgeLimit, graphqueries.DefaultID)
	if err != nil {
		log.Printf("handleStats: snapshot error: %v", err)
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	endpoint := snap.Meta.SparqlEndpoint
	writeJSON(w, http.StatusOK, StatsResponse{
		Backend:        snap.Meta.Backend,
		TTLPath:        snap.Meta.TTLPath,
		SparqlEndpoint: &endpoint,
		ParsedTriples:  len(snap.Edges),
		Nodes:          len(snap.Nodes),
		Edges:          len(snap.Edges),
	})
}

func (s *APIServer) handleSparql(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	var req SparqlQueryRequest
	if err := decodeJSON(r.Body, &req); err != nil || strings.TrimSpace(req.Query) == "" {
		writeError(w, http.StatusUnprocessableEntity, "invalid request body")
		return
	}

	raw, err := s.sparql.Query(r.Context(), req.Query)
	if err != nil {
		writeError(w, http.StatusBadGateway, err.Error())
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(raw)
}

func (s *APIServer) handleGraph(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	nodeLimit, err := intQuery(r, "node_limit", s.cfg.DefaultNodeLimit)
	if err != nil || nodeLimit < 1 || nodeLimit > s.cfg.MaxNodeLimit {
		writeError(w, http.StatusUnprocessableEntity, fmt.Sprintf("node_limit must be between 1 and %d", s.cfg.MaxNodeLimit))
		return
	}
	edgeLimit, err := intQuery(r, "edge_limit", s.cfg.DefaultEdgeLimit)
	if err != nil || edgeLimit < 1 || edgeLimit > s.cfg.MaxEdgeLimit {
		writeError(w, http.StatusUnprocessableEntity, fmt.Sprintf("edge_limit must be between 1 and %d", s.cfg.MaxEdgeLimit))
		return
	}

		graphQueryID := strings.TrimSpace(r.URL.Query().Get("graph_query_id"))
		if graphQueryID == "" {
			graphQueryID = graphqueries.DefaultID
		}
		if _, ok := graphqueries.Get(graphQueryID); !ok {
			writeError(w, http.StatusUnprocessableEntity, "unknown graph_query_id")
			return
		}

	snap, err := s.snapshots.Get(r.Context(), nodeLimit, edgeLimit, graphQueryID)
	if err != nil {
		log.Printf("handleGraph: snapshot error graph_query_id=%s node_limit=%d edge_limit=%d err=%v", graphQueryID, nodeLimit, edgeLimit, err)
		if _, ok := err.(*CycleError); ok {
			writeError(w, http.StatusUnprocessableEntity, err.Error())
			return
		}
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	writeJSON(w, http.StatusOK, snap)
}

func (s *APIServer) handleGraphQueries(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	writeJSON(w, http.StatusOK, graphqueries.List())
}

func (s *APIServer) handleSelectionQueries(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	writeJSON(w, http.StatusOK, selectionqueries.List())
}

func (s *APIServer) handleSelectionQuery(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	var req SelectionQueryRequest
	if err := decodeJSON(r.Body, &req); err != nil || strings.TrimSpace(req.QueryID) == "" {
		writeError(w, http.StatusUnprocessableEntity, "invalid request body")
		return
	}
	if _, ok := selectionqueries.Get(req.QueryID); !ok {
		writeError(w, http.StatusUnprocessableEntity, "unknown query_id")
		return
	}
	if len(req.SelectedIDs) == 0 {
		writeJSON(w, http.StatusOK, SelectionQueryResponse{
			QueryID:     req.QueryID,
			SelectedIDs: req.SelectedIDs,
			NeighborIDs: []uint32{},
		})
		return
	}

	graphQueryID := graphqueries.DefaultID
	if req.GraphQueryID != nil && strings.TrimSpace(*req.GraphQueryID) != "" {
		graphQueryID = strings.TrimSpace(*req.GraphQueryID)
	}
	if _, ok := graphqueries.Get(graphQueryID); !ok {
		writeError(w, http.StatusUnprocessableEntity, "unknown graph_query_id")
		return
	}

	nodeLimit := s.cfg.DefaultNodeLimit
	edgeLimit := s.cfg.DefaultEdgeLimit
	if req.NodeLimit != nil {
		nodeLimit = *req.NodeLimit
	}
	if req.EdgeLimit != nil {
		edgeLimit = *req.EdgeLimit
	}
	if nodeLimit < 1 || nodeLimit > s.cfg.MaxNodeLimit || edgeLimit < 1 || edgeLimit > s.cfg.MaxEdgeLimit {
		writeError(w, http.StatusUnprocessableEntity, "invalid node_limit/edge_limit")
		return
	}

	snap, err := s.snapshots.Get(r.Context(), nodeLimit, edgeLimit, graphQueryID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	ids, err := runSelectionQuery(r.Context(), s.sparql, snap, req.QueryID, req.SelectedIDs, s.cfg.IncludeBNodes)
	if err != nil {
		writeError(w, http.StatusBadGateway, err.Error())
		return
	}

	writeJSON(w, http.StatusOK, SelectionQueryResponse{
		QueryID:     req.QueryID,
		SelectedIDs: req.SelectedIDs,
		NeighborIDs: ids,
	})
}

func (s *APIServer) handleNeighbors(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	var req NeighborsRequest
	if err := decodeJSON(r.Body, &req); err != nil {
		writeError(w, http.StatusUnprocessableEntity, "invalid request body")
		return
	}
	if len(req.SelectedIDs) == 0 {
		writeJSON(w, http.StatusOK, NeighborsResponse{SelectedIDs: req.SelectedIDs, NeighborIDs: []uint32{}})
		return
	}

	graphQueryID := graphqueries.DefaultID
	if req.GraphQueryID != nil && strings.TrimSpace(*req.GraphQueryID) != "" {
		graphQueryID = strings.TrimSpace(*req.GraphQueryID)
	}
	if _, ok := graphqueries.Get(graphQueryID); !ok {
		writeError(w, http.StatusUnprocessableEntity, "unknown graph_query_id")
		return
	}

	nodeLimit := s.cfg.DefaultNodeLimit
	edgeLimit := s.cfg.DefaultEdgeLimit
	if req.NodeLimit != nil {
		nodeLimit = *req.NodeLimit
	}
	if req.EdgeLimit != nil {
		edgeLimit = *req.EdgeLimit
	}
	if nodeLimit < 1 || nodeLimit > s.cfg.MaxNodeLimit || edgeLimit < 1 || edgeLimit > s.cfg.MaxEdgeLimit {
		writeError(w, http.StatusUnprocessableEntity, "invalid node_limit/edge_limit")
		return
	}

	snap, err := s.snapshots.Get(r.Context(), nodeLimit, edgeLimit, graphQueryID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	nbrs, err := runSelectionQuery(r.Context(), s.sparql, snap, "neighbors", req.SelectedIDs, s.cfg.IncludeBNodes)
	if err != nil {
		writeError(w, http.StatusBadGateway, err.Error())
		return
	}

	writeJSON(w, http.StatusOK, NeighborsResponse{SelectedIDs: req.SelectedIDs, NeighborIDs: nbrs})
}

func intQuery(r *http.Request, name string, def int) (int, error) {
	raw := strings.TrimSpace(r.URL.Query().Get(name))
	if raw == "" {
		return def, nil
	}
	n, err := strconv.Atoi(raw)
	if err != nil {
		return 0, err
	}
	return n, nil
}
