// Copyright 2022 The searKing Author. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package webhdfs

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/searKing/golang/go/exp/types"

	"github.com/searKing/golang/go/errors"
)

type SetXAttrRequest struct {
	Authentication
	ProxyUser
	CSRF
	HttpRequest

	// Path of the object to get.
	//
	// Path is a required field
	Path *string `validate:"required"`

	// Name				xattr.name
	// Description		The XAttr name of a file/directory.
	// Type				String
	// Default Value	<empty>
	// Valid Values		Any string prefixed with user./trusted./system./security..
	// Syntax			Any string prefixed with user./trusted./system./security..
	XAttrName *string `validate:"required"`
	// Name				xattr.value
	// Description		The XAttr value of a file/directory.
	// Type				String
	// Default Value	<empty>
	// Valid Values		An encoded value.
	// Syntax			Enclosed in double quotes or prefixed with 0x or 0s.
	XAttrValue *string `validate:"required"`
	// Name	flag
	// Description	The XAttr set flag.
	// Type	String
	// Default Value	<empty>
	// Valid Values	CREATE,REPLACE.
	// Syntax	CREATE,REPLACE.
	XAttrFlag *XAttrSetFlag `validate:"required"`
}

type SetXAttrResponse struct {
	NameNode string `json:"-"`
	ErrorResponse
	HttpResponse `json:"-"`
}

func (req *SetXAttrRequest) RawPath() string {
	return types.Value(req.Path)
}
func (req *SetXAttrRequest) RawQuery() string {
	v := url.Values{}
	v.Set("op", OpSetXAttr)
	if req.Authentication.Delegation != nil {
		v.Set("delegation", types.Value(req.Authentication.Delegation))
	}
	if req.ProxyUser.Username != nil {
		v.Set("user.name", types.Value(req.ProxyUser.Username))
	}
	if req.ProxyUser.DoAs != nil {
		v.Set("doas", types.Value(req.ProxyUser.DoAs))
	}

	if req.XAttrName != nil {
		v.Set("xattr.name", types.Value(req.XAttrName))
	}
	if req.XAttrValue != nil {
		v.Set("xattr.value", types.Value(req.XAttrValue))
	}
	if req.XAttrFlag != nil {
		v.Set("flag", types.Value((*string)(req.XAttrFlag)))
	}
	return v.Encode()
}

func (resp *SetXAttrResponse) UnmarshalHTTP(httpResp *http.Response) error {
	resp.HttpResponse.UnmarshalHTTP(httpResp)
	if isSuccessHttpCode(httpResp.StatusCode) {
		return nil
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if len(body) == 0 {
		return ErrorFromHttpResponse(httpResp)
	}
	if err = json.Unmarshal(body, &resp); err != nil {
		return err
	}

	if err := resp.Exception(); err != nil {
		return err
	}
	return nil
}

// Set XAttr
// See: https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/WebHDFS.html#Set_XAttr
func (c *Client) SetXAttr(req *SetXAttrRequest) (*SetXAttrResponse, error) {
	return c.setXAttr(nil, req)
}
func (c *Client) SetXAttrWithContext(ctx context.Context, req *SetXAttrRequest) (*SetXAttrResponse, error) {
	if ctx == nil {
		panic("nil context")
	}
	return c.setXAttr(ctx, req)
}
func (c *Client) setXAttr(ctx context.Context, req *SetXAttrRequest) (*SetXAttrResponse, error) {
	err := c.opts.Validator.Struct(req)
	if err != nil {
		return nil, err
	}

	nameNodes := c.opts.Addresses
	if nameNodes == nil {
		return nil, fmt.Errorf("missing namenode addresses")
	}
	var u = c.HttpUrl(req)

	var errs []error
	for _, addr := range nameNodes {
		u.Host = addr

		httpReq, err := http.NewRequest(http.MethodPut, u.String(), nil)
		if err != nil {
			return nil, err
		}
		httpReq.Close = req.HttpRequest.Close
		if req.CSRF.XXsrfHeader != nil {
			httpReq.Header.Set("X-XSRF-HEADER", types.Value(req.CSRF.XXsrfHeader))
		}

		if ctx != nil {
			httpReq = httpReq.WithContext(ctx)
		}
		if req.HttpRequest.PreSendHandler != nil {
			httpReq, err = req.HttpRequest.PreSendHandler(httpReq)
			if err != nil {
				return nil, fmt.Errorf("pre send handled: %w", err)
			}
		}

		httpResp, err := c.httpClient().Do(httpReq)
		if err != nil {
			errs = append(errs, err)
			continue
		}

		var resp SetXAttrResponse
		resp.NameNode = addr

		if err := resp.UnmarshalHTTP(httpResp); err != nil {
			errs = append(errs, err)
			continue
		}
		return &resp, nil
	}
	return nil, errors.Multi(errs...)
}