package pkger

import (
	"context"
	"net/http"

	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/pkg/httpc"
)

// HTTPRemoteService provides an http client that is fluent in all things pkger.
type HTTPRemoteService struct {
	Client *httpc.Client
}

var _ SVC = (*HTTPRemoteService)(nil)

func (s *HTTPRemoteService) InitStack(ctx context.Context, userID influxdb.ID, stack Stack) (Stack, error) {
	reqBody := ReqCreateStack{
		OrgID:       stack.OrgID.String(),
		Name:        stack.Name,
		Description: stack.Description,
		URLs:        stack.URLs,
	}

	var respBody RespCreateStack
	err := s.Client.
		PostJSON(reqBody, RoutePrefix, "/stacks").
		DecodeJSON(&respBody).
		Do(ctx)
	if err != nil {
		return Stack{}, err
	}

	newStack := Stack{
		Name:        respBody.Name,
		Description: respBody.Description,
		URLs:        respBody.URLs,
		Resources:   make([]StackResource, 0),
		CRUDLog:     respBody.CRUDLog,
	}

	id, err := influxdb.IDFromString(respBody.ID)
	if err != nil {
		return Stack{}, err
	}
	newStack.ID = *id

	orgID, err := influxdb.IDFromString(respBody.OrgID)
	if err != nil {
		return Stack{}, err
	}
	newStack.OrgID = *orgID

	return newStack, nil
}

func (s *HTTPRemoteService) DeleteStack(ctx context.Context, identifiers struct{ OrgID, UserID, StackID influxdb.ID }) error {
	return s.Client.
		Delete(RoutePrefix, "stacks", identifiers.StackID.String()).
		QueryParams([2]string{"orgID", identifiers.OrgID.String()}).
		Do(ctx)
}

func (s *HTTPRemoteService) ListStacks(ctx context.Context, orgID influxdb.ID, f ListFilter) ([]Stack, error) {
	queryParams := [][2]string{{"orgID", orgID.String()}}
	for _, name := range f.Names {
		queryParams = append(queryParams, [2]string{"name", name})
	}
	for _, stackID := range f.StackIDs {
		queryParams = append(queryParams, [2]string{"stackID", stackID.String()})
	}

	var resp RespListStacks
	err := s.Client.
		Get(RoutePrefix, "/stacks").
		QueryParams(queryParams...).
		DecodeJSON(&resp).
		Do(ctx)
	if err != nil {
		return nil, err
	}
	return resp.Stacks, nil
}

// CreatePkg will produce a pkg from the parameters provided.
func (s *HTTPRemoteService) CreatePkg(ctx context.Context, setters ...CreatePkgSetFn) (*Pkg, error) {
	var opt CreateOpt
	for _, setter := range setters {
		if err := setter(&opt); err != nil {
			return nil, err
		}
	}

	var orgIDs []ReqCreateOrgIDOpt
	for _, org := range opt.OrgIDs {
		orgIDs = append(orgIDs, ReqCreateOrgIDOpt{
			OrgID: org.OrgID.String(),
			Filters: struct {
				ByLabel        []string `json:"byLabel"`
				ByResourceKind []Kind   `json:"byResourceKind"`
			}{
				ByLabel:        org.LabelNames,
				ByResourceKind: org.ResourceKinds,
			},
		})
	}

	reqBody := ReqCreatePkg{
		OrgIDs:    orgIDs,
		Resources: opt.Resources,
	}

	var newPkg *Pkg
	err := s.Client.
		PostJSON(reqBody, RoutePrefix).
		Decode(func(resp *http.Response) error {
			pkg, err := Parse(EncodingJSON, FromReader(resp.Body))
			newPkg = pkg
			return err
		}).
		Do(ctx)
	if err != nil {
		return nil, err
	}

	if err := newPkg.Validate(ValidWithoutResources()); err != nil {
		return nil, err
	}
	return newPkg, nil
}

// DryRun provides a dry run of the pkg application. The pkg will be marked verified
// for later calls to Apply. This func will be run on an Apply if it has not been run
// already.
func (s *HTTPRemoteService) DryRun(ctx context.Context, orgID, userID influxdb.ID, pkg *Pkg, opts ...ApplyOptFn) (PkgImpactSummary, error) {
	return s.apply(ctx, orgID, pkg, true, opts...)
}

// Apply will apply all the resources identified in the provided pkg. The entire pkg will be applied
// in its entirety. If a failure happens midway then the entire pkg will be rolled back to the state
// from before the pkg was applied.
func (s *HTTPRemoteService) Apply(ctx context.Context, orgID, userID influxdb.ID, pkg *Pkg, opts ...ApplyOptFn) (PkgImpactSummary, error) {
	return s.apply(ctx, orgID, pkg, false, opts...)
}

func (s *HTTPRemoteService) apply(ctx context.Context, orgID influxdb.ID, pkg *Pkg, dryRun bool, opts ...ApplyOptFn) (PkgImpactSummary, error) {
	opt := applyOptFromOptFns(opts...)

	var rawPkg []byte
	if pkg != nil {
		b, err := pkg.Encode(EncodingJSON)
		if err != nil {
			return PkgImpactSummary{}, err
		}
		rawPkg = b
	}

	reqBody := ReqApplyPkg{
		OrgID:   orgID.String(),
		DryRun:  dryRun,
		EnvRefs: opt.EnvRefs,
		Secrets: opt.MissingSecrets,
		RawPkg:  rawPkg,
	}
	if opt.StackID != 0 {
		stackID := opt.StackID.String()
		reqBody.StackID = &stackID
	}

	var resp RespApplyPkg
	err := s.Client.
		PostJSON(reqBody, RoutePrefix, "/apply").
		DecodeJSON(&resp).
		Do(ctx)
	if err != nil {
		return PkgImpactSummary{}, err
	}

	return PkgImpactSummary{
		Diff:    resp.Diff,
		Summary: resp.Summary,
	}, NewParseError(resp.Errors...)
}
