// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package agent

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

	"github.com/hashicorp/nomad/nomad/structs"
)

func (s *HTTPServer) NodePoolsRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
	switch req.Method {
	case http.MethodGet:
		return s.nodePoolList(resp, req)
	case http.MethodPut, http.MethodPost:
		return s.nodePoolUpsert(resp, req, "")
	default:
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
}

func (s *HTTPServer) NodePoolSpecificRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
	path := strings.TrimPrefix(req.URL.Path, "/v1/node/pool/")
	switch {
	case strings.HasSuffix(path, "/nodes"):
		poolName := strings.TrimSuffix(path, "/nodes")
		return s.nodePoolNodesList(resp, req, poolName)
	case strings.HasSuffix(path, "/jobs"):
		poolName := strings.TrimSuffix(path, "/jobs")
		return s.nodePoolJobList(resp, req, poolName)
	default:
		return s.nodePoolCRUD(resp, req, path)
	}
}

func (s *HTTPServer) nodePoolCRUD(resp http.ResponseWriter, req *http.Request, poolName string) (any, error) {
	switch req.Method {
	case http.MethodGet:
		return s.nodePoolQuery(resp, req, poolName)
	case http.MethodPut, http.MethodPost:
		return s.nodePoolUpsert(resp, req, poolName)
	case http.MethodDelete:
		return s.nodePoolDelete(resp, req, poolName)
	default:
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
}

func (s *HTTPServer) nodePoolList(resp http.ResponseWriter, req *http.Request) (any, error) {
	args := structs.NodePoolListRequest{}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.NodePoolListResponse
	if err := s.agent.RPC("NodePool.List", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.NodePools == nil {
		out.NodePools = make([]*structs.NodePool, 0)
	}
	return out.NodePools, nil
}

func (s *HTTPServer) nodePoolQuery(resp http.ResponseWriter, req *http.Request, poolName string) (any, error) {
	args := structs.NodePoolSpecificRequest{
		Name: poolName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.SingleNodePoolResponse
	if err := s.agent.RPC("NodePool.GetNodePool", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.NodePool == nil {
		return nil, CodedError(http.StatusNotFound, "node pool not found")
	}

	return out.NodePool, nil
}

func (s *HTTPServer) nodePoolUpsert(resp http.ResponseWriter, req *http.Request, poolName string) (any, error) {
	var pool structs.NodePool
	if err := decodeBody(req, &pool); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	if poolName != "" && pool.Name != poolName {
		return nil, CodedError(http.StatusBadRequest, "Node pool name does not match request path")
	}

	args := structs.NodePoolUpsertRequest{
		NodePools: []*structs.NodePool{&pool},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("NodePool.UpsertNodePools", &args, &out); err != nil {
		return nil, err
	}

	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) nodePoolDelete(resp http.ResponseWriter, req *http.Request, poolName string) (any, error) {
	args := structs.NodePoolDeleteRequest{
		Names: []string{poolName},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("NodePool.DeleteNodePools", &args, &out); err != nil {
		return nil, err
	}

	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) nodePoolNodesList(resp http.ResponseWriter, req *http.Request, poolName string) (interface{}, error) {
	if req.Method != http.MethodGet {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	args := structs.NodePoolNodesRequest{
		Name: poolName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	// Parse node fields selection.
	fields, err := parseNodeListStubFields(req)
	if err != nil {
		return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Failed to parse node list fields: %v", err))
	}
	args.Fields = fields

	if args.Prefix != "" {
		// the prefix argument is ambiguous for this endpoint (does it refer to
		// the node pool name or the node IDs like /v1/nodes?) so the RPC
		// handler ignores it
		return nil, CodedError(http.StatusBadRequest, "prefix argument not allowed")
	}

	var out structs.NodePoolNodesResponse
	if err := s.agent.RPC("NodePool.ListNodes", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Nodes == nil {
		out.Nodes = make([]*structs.NodeListStub, 0)
	}
	return out.Nodes, nil
}

func (s *HTTPServer) nodePoolJobList(resp http.ResponseWriter, req *http.Request, poolName string) (any, error) {
	if req.Method != http.MethodGet {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	args := structs.NodePoolJobsRequest{
		Name: poolName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	if args.Prefix != "" {
		// the prefix argument is ambiguous for this endpoint (does it refer to
		// the node pool name or the job names like /v1/jobs?) so the RPC
		// handler ignores it
		return nil, CodedError(http.StatusBadRequest, "prefix argument not allowed")
	}

	// Parse meta query param
	args.Fields = &structs.JobStubFields{}
	jobMeta, err := parseBool(req, "meta")
	if err != nil {
		return nil, err
	}
	if jobMeta != nil {
		args.Fields.Meta = *jobMeta
	}

	var out structs.NodePoolJobsResponse
	if err := s.agent.RPC("NodePool.ListJobs", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Jobs == nil {
		out.Jobs = make([]*structs.JobListStub, 0)
	}
	return out.Jobs, nil
}
