package godo

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

const (
	functionsBasePath          = "/v2/functions/namespaces"
	functionsNamespacePath     = functionsBasePath + "/%s"
	functionsTriggerBasePath   = functionsNamespacePath + "/triggers"
	functionsAccessKeyBasePath = functionsNamespacePath + "/keys"
)

type FunctionsService interface {
	ListNamespaces(context.Context) ([]FunctionsNamespace, *Response, error)
	GetNamespace(context.Context, string) (*FunctionsNamespace, *Response, error)
	CreateNamespace(context.Context, *FunctionsNamespaceCreateRequest) (*FunctionsNamespace, *Response, error)
	DeleteNamespace(context.Context, string) (*Response, error)

	ListTriggers(context.Context, string) ([]FunctionsTrigger, *Response, error)
	GetTrigger(context.Context, string, string) (*FunctionsTrigger, *Response, error)
	CreateTrigger(context.Context, string, *FunctionsTriggerCreateRequest) (*FunctionsTrigger, *Response, error)
	UpdateTrigger(context.Context, string, string, *FunctionsTriggerUpdateRequest) (*FunctionsTrigger, *Response, error)
	DeleteTrigger(context.Context, string, string) (*Response, error)

	ListAccessKeys(context.Context, string) ([]FunctionsAccessKey, *Response, error)
	CreateAccessKey(context.Context, string, *FunctionsAccessKeyCreateRequest) (*FunctionsAccessKey, *Response, error)
	UpdateAccessKey(context.Context, string, string, *FunctionsAccessKeyUpdateRequest) (*FunctionsAccessKey, *Response, error)
	DeleteAccessKey(context.Context, string, string) (*Response, error)
}

type FunctionsServiceOp struct {
	client *Client
}

var _ FunctionsService = &FunctionsServiceOp{}

type namespacesRoot struct {
	Namespaces []FunctionsNamespace `json:"namespaces,omitempty"`
}

type namespaceRoot struct {
	Namespace *FunctionsNamespace `json:"namespace,omitempty"`
}

type FunctionsNamespace struct {
	ApiHost   string    `json:"api_host,omitempty"`
	Namespace string    `json:"namespace,omitempty"`
	CreatedAt time.Time `json:"created_at,omitempty"`
	UpdatedAt time.Time `json:"updated_at,omitempty"`
	Label     string    `json:"label,omitempty"`
	Region    string    `json:"region,omitempty"`
	UUID      string    `json:"uuid,omitempty"`
	Key       string    `json:"key,omitempty"`
}

type FunctionsNamespaceCreateRequest struct {
	Label  string `json:"label"`
	Region string `json:"region"`
}

type triggersRoot struct {
	Triggers []FunctionsTrigger `json:"triggers,omitempty"`
}

type triggerRoot struct {
	Trigger *FunctionsTrigger `json:"trigger,omitempty"`
}

type FunctionsTrigger struct {
	Namespace        string                   `json:"namespace,omitempty"`
	Function         string                   `json:"function,omitempty"`
	Type             string                   `json:"type,omitempty"`
	Name             string                   `json:"name,omitempty"`
	IsEnabled        bool                     `json:"is_enabled"`
	CreatedAt        time.Time                `json:"created_at,omitempty"`
	UpdatedAt        time.Time                `json:"updated_at,omitempty"`
	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
	ScheduledRuns    *TriggerScheduledRuns    `json:"scheduled_runs,omitempty"`
}

type TriggerScheduledDetails struct {
	Cron string                 `json:"cron,omitempty"`
	Body map[string]interface{} `json:"body,omitempty"`
}

type TriggerScheduledRuns struct {
	LastRunAt time.Time `json:"last_run_at,omitempty"`
	NextRunAt time.Time `json:"next_run_at,omitempty"`
}

type FunctionsTriggerCreateRequest struct {
	Name             string                   `json:"name"`
	Type             string                   `json:"type"`
	Function         string                   `json:"function"`
	IsEnabled        bool                     `json:"is_enabled"`
	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
}

type FunctionsTriggerUpdateRequest struct {
	IsEnabled        *bool                    `json:"is_enabled,omitempty"`
	ScheduledDetails *TriggerScheduledDetails `json:"scheduled_details,omitempty"`
}

type accessKeysRoot struct {
	AccessKeys []FunctionsAccessKey `json:"access_keys,omitempty"`
	Count      int                  `json:"count,omitempty"`
}

type accessKeyRoot struct {
	AccessKey *FunctionsAccessKey `json:"access_key,omitempty"`
}

type FunctionsAccessKey struct {
	ID         string    `json:"id,omitempty"`
	Name       string    `json:"name,omitempty"`
	Secret     string    `json:"secret,omitempty"`
	ExpiresAt  time.Time `json:"expires_at,omitempty"`
	CreatedAt  time.Time `json:"created_at,omitempty"`
	UpdatedAt  time.Time `json:"updated_at,omitempty"`
	LastUsedAt time.Time `json:"last_used_at,omitempty"`
}

type FunctionsAccessKeyCreateRequest struct {
	Name      string `json:"name"`
	ExpiresIn string `json:"expires_in,omitempty"`
}

type FunctionsAccessKeyUpdateRequest struct {
	Name string `json:"name"`
}

// Gets a list of namespaces
func (s *FunctionsServiceOp) ListNamespaces(ctx context.Context) ([]FunctionsNamespace, *Response, error) {
	req, err := s.client.NewRequest(ctx, http.MethodGet, functionsBasePath, nil)
	if err != nil {
		return nil, nil, err
	}
	nsRoot := new(namespacesRoot)
	resp, err := s.client.Do(ctx, req, nsRoot)
	if err != nil {
		return nil, resp, err
	}
	return nsRoot.Namespaces, resp, nil
}

// Gets a single namespace
func (s *FunctionsServiceOp) GetNamespace(ctx context.Context, namespace string) (*FunctionsNamespace, *Response, error) {
	path := fmt.Sprintf(functionsNamespacePath, namespace)

	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}
	nsRoot := new(namespaceRoot)
	resp, err := s.client.Do(ctx, req, nsRoot)
	if err != nil {
		return nil, resp, err
	}
	return nsRoot.Namespace, resp, nil
}

// Creates a namespace
func (s *FunctionsServiceOp) CreateNamespace(ctx context.Context, opts *FunctionsNamespaceCreateRequest) (*FunctionsNamespace, *Response, error) {
	req, err := s.client.NewRequest(ctx, http.MethodPost, functionsBasePath, opts)
	if err != nil {
		return nil, nil, err
	}
	nsRoot := new(namespaceRoot)
	resp, err := s.client.Do(ctx, req, nsRoot)
	if err != nil {
		return nil, resp, err
	}
	return nsRoot.Namespace, resp, nil
}

// Delete a namespace
func (s *FunctionsServiceOp) DeleteNamespace(ctx context.Context, namespace string) (*Response, error) {
	path := fmt.Sprintf(functionsNamespacePath, namespace)

	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(ctx, req, nil)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

// ListTriggers gets a list of triggers
func (s *FunctionsServiceOp) ListTriggers(ctx context.Context, namespace string) ([]FunctionsTrigger, *Response, error) {
	path := fmt.Sprintf(functionsTriggerBasePath, namespace)
	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}
	root := new(triggersRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Triggers, resp, nil
}

// GetTrigger gets a single trigger
func (s *FunctionsServiceOp) GetTrigger(ctx context.Context, namespace string, trigger string) (*FunctionsTrigger, *Response, error) {
	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}
	root := new(triggerRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Trigger, resp, nil
}

// CreateTrigger creates a trigger
func (s *FunctionsServiceOp) CreateTrigger(ctx context.Context, namespace string, opts *FunctionsTriggerCreateRequest) (*FunctionsTrigger, *Response, error) {
	path := fmt.Sprintf(functionsTriggerBasePath, namespace)
	req, err := s.client.NewRequest(ctx, http.MethodPost, path, opts)
	if err != nil {
		return nil, nil, err
	}
	root := new(triggerRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Trigger, resp, nil
}

// UpdateTrigger updates a trigger
func (s *FunctionsServiceOp) UpdateTrigger(ctx context.Context, namespace string, trigger string, opts *FunctionsTriggerUpdateRequest) (*FunctionsTrigger, *Response, error) {
	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
	req, err := s.client.NewRequest(ctx, http.MethodPut, path, opts)

	if err != nil {
		return nil, nil, err
	}
	root := new(triggerRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Trigger, resp, nil
}

// DeleteTrigger deletes a trigger
func (s *FunctionsServiceOp) DeleteTrigger(ctx context.Context, namespace string, trigger string) (*Response, error) {
	path := fmt.Sprintf(functionsTriggerBasePath+"/%s", namespace, trigger)
	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)

	if err != nil {
		return nil, err
	}
	resp, err := s.client.Do(ctx, req, nil)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

// ListAccessKeys lists access keys for a namespace
func (s *FunctionsServiceOp) ListAccessKeys(ctx context.Context, namespace string) ([]FunctionsAccessKey, *Response, error) {
	path := fmt.Sprintf(functionsAccessKeyBasePath, namespace)
	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}
	root := new(accessKeysRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.AccessKeys, resp, nil
}

// CreateAccessKey creates an access key for a namespace
func (s *FunctionsServiceOp) CreateAccessKey(ctx context.Context, namespace string, opts *FunctionsAccessKeyCreateRequest) (*FunctionsAccessKey, *Response, error) {
	path := fmt.Sprintf(functionsAccessKeyBasePath, namespace)
	req, err := s.client.NewRequest(ctx, http.MethodPost, path, opts)
	if err != nil {
		return nil, nil, err
	}
	root := new(accessKeyRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.AccessKey, resp, nil
}

// UpdateAccessKey updates an access key for a namespace
func (s *FunctionsServiceOp) UpdateAccessKey(ctx context.Context, namespace string, keyID string, opts *FunctionsAccessKeyUpdateRequest) (*FunctionsAccessKey, *Response, error) {
	path := fmt.Sprintf(functionsAccessKeyBasePath+"/%s", namespace, keyID)
	req, err := s.client.NewRequest(ctx, http.MethodPut, path, opts)
	if err != nil {
		return nil, nil, err
	}
	root := new(accessKeyRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.AccessKey, resp, nil
}

// DeleteAccessKey deletes an access key for a namespace
func (s *FunctionsServiceOp) DeleteAccessKey(ctx context.Context, namespace string, keyID string) (*Response, error) {
	path := fmt.Sprintf(functionsAccessKeyBasePath+"/%s", namespace, keyID)
	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}
	resp, err := s.client.Do(ctx, req, nil)
	if err != nil {
		return resp, err
	}
	return resp, nil
}
