package agent

import (
	"net/http"
	"strings"

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

func (s *HTTPServer) ACLPoliciesRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != "GET" {
		return nil, CodedError(405, ErrInvalidMethod)
	}

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

	var out structs.ACLPolicyListResponse
	if err := s.agent.RPC("ACL.ListPolicies", &args, &out); err != nil {
		return nil, err
	}

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

func (s *HTTPServer) ACLPolicySpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	name := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/")
	if len(name) == 0 {
		return nil, CodedError(400, "Missing Policy Name")
	}
	switch req.Method {
	case "GET":
		return s.aclPolicyQuery(resp, req, name)
	case "PUT", "POST":
		return s.aclPolicyUpdate(resp, req, name)
	case "DELETE":
		return s.aclPolicyDelete(resp, req, name)
	default:
		return nil, CodedError(405, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclPolicyQuery(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {
	args := structs.ACLPolicySpecificRequest{
		Name: policyName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.SingleACLPolicyResponse
	if err := s.agent.RPC("ACL.GetPolicy", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Policy == nil {
		return nil, CodedError(404, "ACL policy not found")
	}
	return out.Policy, nil
}

func (s *HTTPServer) aclPolicyUpdate(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {
	// Parse the policy
	var policy structs.ACLPolicy
	if err := decodeBody(req, &policy); err != nil {
		return nil, CodedError(500, err.Error())
	}

	// Ensure the policy name matches
	if policy.Name != policyName {
		return nil, CodedError(400, "ACL policy name does not match request path")
	}

	// Format the request
	args := structs.ACLPolicyUpsertRequest{
		Policies: []*structs.ACLPolicy{&policy},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.UpsertPolicies", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) aclPolicyDelete(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {

	args := structs.ACLPolicyDeleteRequest{
		Names: []string{policyName},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.DeletePolicies", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) ACLTokensRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != "GET" {
		return nil, CodedError(405, ErrInvalidMethod)
	}

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

	var out structs.ACLTokenListResponse
	if err := s.agent.RPC("ACL.ListTokens", &args, &out); err != nil {
		return nil, err
	}

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

func (s *HTTPServer) ACLTokenBootstrap(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	// Ensure this is a PUT or POST
	if !(req.Method == "PUT" || req.Method == "POST") {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	// Format the request
	args := structs.ACLTokenBootstrapRequest{}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLTokenUpsertResponse
	if err := s.agent.RPC("ACL.Bootstrap", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	if len(out.Tokens) > 0 {
		return out.Tokens[0], nil
	}
	return nil, nil
}

func (s *HTTPServer) ACLTokenSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	path := req.URL.Path

	switch path {
	case "/v1/acl/token":
		if !(req.Method == "PUT" || req.Method == "POST") {
			return nil, CodedError(405, ErrInvalidMethod)
		}
		return s.aclTokenUpdate(resp, req, "")
	case "/v1/acl/token/self":
		return s.aclTokenSelf(resp, req)
	}

	accessor := strings.TrimPrefix(path, "/v1/acl/token/")
	return s.aclTokenCrud(resp, req, accessor)
}

func (s *HTTPServer) aclTokenCrud(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	if tokenAccessor == "" {
		return nil, CodedError(400, "Missing Token Accessor")
	}

	switch req.Method {
	case "GET":
		return s.aclTokenQuery(resp, req, tokenAccessor)
	case "PUT", "POST":
		return s.aclTokenUpdate(resp, req, tokenAccessor)
	case "DELETE":
		return s.aclTokenDelete(resp, req, tokenAccessor)
	default:
		return nil, CodedError(405, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclTokenQuery(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	args := structs.ACLTokenSpecificRequest{
		AccessorID: tokenAccessor,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.SingleACLTokenResponse
	if err := s.agent.RPC("ACL.GetToken", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Token == nil {
		return nil, CodedError(404, "ACL token not found")
	}
	return out.Token, nil
}

func (s *HTTPServer) aclTokenSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != "GET" {
		return nil, CodedError(405, ErrInvalidMethod)
	}
	args := structs.ResolveACLTokenRequest{}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	args.SecretID = args.AuthToken

	var out structs.ResolveACLTokenResponse
	if err := s.agent.RPC("ACL.ResolveToken", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Token == nil {
		return nil, CodedError(404, "ACL token not found")
	}
	return out.Token, nil
}

func (s *HTTPServer) aclTokenUpdate(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	// Parse the token
	var token structs.ACLToken
	if err := decodeBody(req, &token); err != nil {
		return nil, CodedError(500, err.Error())
	}

	// Ensure the token accessor matches
	if tokenAccessor != "" && (token.AccessorID != tokenAccessor) {
		return nil, CodedError(400, "ACL token accessor does not match request path")
	}

	// Format the request
	args := structs.ACLTokenUpsertRequest{
		Tokens: []*structs.ACLToken{&token},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLTokenUpsertResponse
	if err := s.agent.RPC("ACL.UpsertTokens", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	if len(out.Tokens) > 0 {
		return out.Tokens[0], nil
	}
	return nil, nil
}

func (s *HTTPServer) aclTokenDelete(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {

	args := structs.ACLTokenDeleteRequest{
		AccessorIDs: []string{tokenAccessor},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.DeleteTokens", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}
