package serve

import (
	"axolotl/models"
	"axolotl/service"
	"axolotl/store"
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
)

// New returns an HTTP handler that exposes NodeService as a JSON API.
// When oidcCfg is non-nil, every request must carry a valid Bearer token;
// the authenticated username is derived from the token claim configured in
// OIDCConfig.UserClaim. Without OIDC, the X-Ax-User header is used instead.
func New(newSvc func(user string) (service.NodeService, error), oidcCfg *store.OIDCConfig) (http.Handler, error) {
	s := &server{newSvc: newSvc}
	mux := http.NewServeMux()
	mux.HandleFunc("GET /nodes", s.listNodes)
	mux.HandleFunc("POST /nodes", s.addNode)
	mux.HandleFunc("GET /nodes/{id}", s.getNode)
	mux.HandleFunc("PATCH /nodes/{id}", s.updateNode)
	mux.HandleFunc("DELETE /nodes/{id}", s.deleteNode)
	mux.HandleFunc("GET /users", s.listUsers)
	mux.HandleFunc("POST /users", s.addUser)
	if oidcCfg != nil {
		ah, err := newAuthHandler(*oidcCfg)
		if err != nil {
			return nil, err
		}
		mux.HandleFunc("POST /auth/start", ah.start)
		mux.HandleFunc("POST /auth/device/start", ah.deviceStart)
		mux.HandleFunc("GET /auth/callback", ah.callback)
		mux.HandleFunc("GET /auth/poll", ah.poll)
		return withSessionAuth(ah, mux), nil
	}
	return mux, nil
}

type server struct {
	newSvc func(user string) (service.NodeService, error)
}

func (s *server) svc(w http.ResponseWriter, r *http.Request) (service.NodeService, bool) {
	user := userFromContext(r)
	if user == "" {
		user = r.Header.Get("X-Ax-User")
	}
	if user == "" {
		writeError(w, http.StatusUnauthorized, "X-Ax-User header required")
		return nil, false
	}
	svc, err := s.newSvc(user)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return nil, false
	}
	return svc, true
}

func (s *server) listNodes(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	q := r.URL.Query()
	var filter service.ListFilter
	for _, tag := range q["tag"] {
		filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelType(tag)})
	}
	for _, rel := range q["rel"] {
		filter.Rels = append(filter.Rels, parseRel(rel))
	}
	for k, prefix := range map[string]string{"type": "_type::", "status": "_status::", "prio": "_prio::"} {
		if v := q.Get(k); v != "" {
			filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelType(prefix + v)})
		}
	}
	if v := q.Get("namespace"); v != "" {
		filter.Namespace = v
	}
	if v := q.Get("assignee"); v != "" {
		filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelAssignee, Target: v})
	}
	if v := q.Get("mention"); v != "" {
		filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelMentions, Target: v})
	}
	if q.Get("has_due_date") == "true" {
		filter.HasDueDate = true
	}
	if v := q.Get("due_within"); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			filter.DueWithin = &n
		}
	}
	nodes, err := svc.List(filter)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	writeJSON(w, nodes)
}

func (s *server) addNode(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	var input service.AddInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	n, err := svc.Add(input)
	if err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	w.WriteHeader(http.StatusCreated)
	writeJSON(w, n)
}

func (s *server) getNode(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	n, err := svc.GetByID(r.PathValue("id"))
	if err != nil {
		writeError(w, http.StatusNotFound, err.Error())
		return
	}
	writeJSON(w, n)
}

func (s *server) updateNode(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	var input service.UpdateInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	n, err := svc.Update(r.PathValue("id"), input)
	if err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	writeJSON(w, n)
}

func (s *server) deleteNode(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	if err := svc.Delete(r.PathValue("id")); err != nil {
		writeError(w, http.StatusNotFound, err.Error())
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (s *server) listUsers(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	users, err := svc.ListUsers()
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	writeJSON(w, users)
}

func (s *server) addUser(w http.ResponseWriter, r *http.Request) {
	svc, ok := s.svc(w, r)
	if !ok {
		return
	}
	var body struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	n, err := svc.AddUser(body.Name)
	if err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	w.WriteHeader(http.StatusCreated)
	writeJSON(w, n)
}

func parseRel(s string) service.RelInput {
	if strings.Contains(s, "::") {
		return service.RelInput{Type: models.RelType(s)}
	}

	if before, after, found := strings.Cut(s, ":"); found {
		return service.RelInput{Type: models.RelType(before), Target: after}
	}
	return service.RelInput{Type: models.RelType(s)}
}

func writeJSON(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(v)
}

func writeError(w http.ResponseWriter, code int, msg string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(map[string]string{"error": msg})
}
