package influxdb

import (
	"context"
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/kit/platform/errors"
)

var (
	ErrOrgIDRequired  = fieldRequiredError("OrgID")
	ErrNameRequired   = fieldRequiredError("Name")
	ErrSpecRequired   = fieldRequiredError("Spec")
	ErrOffsetNegative = &errors.Error{
		Code: errors.EInvalid,
		Msg:  "offset cannot be negative",
	}
	ErrLimitLTEZero = &errors.Error{
		Code: errors.EInvalid,
		Msg:  "limit cannot be less-than or equal-to zero",
	}
	ErrNotebookNotFound = &errors.Error{
		Code: errors.ENotFound,
		Msg:  "notebook not found",
	}
)

func fieldRequiredError(field string) error {
	return &errors.Error{
		Code: errors.EInvalid,
		Msg:  fmt.Sprintf("%s required", field),
	}
}

// Notebook represents all visual and query data for a notebook.
type Notebook struct {
	OrgID     platform.ID  `json:"orgID" db:"org_id"`
	ID        platform.ID  `json:"id" db:"id"`
	Name      string       `json:"name" db:"name"`
	Spec      NotebookSpec `json:"spec" db:"spec"`
	CreatedAt time.Time    `json:"createdAt" db:"created_at"`
	UpdatedAt time.Time    `json:"updatedAt" db:"updated_at"`
}

// NotebookSpec is an abitrary JSON object provided by the client.
type NotebookSpec map[string]interface{}

// Value implements the database/sql Valuer interface for adding NotebookSpecs to the database.
func (s NotebookSpec) Value() (driver.Value, error) {
	spec, err := json.Marshal(s)
	if err != nil {
		return nil, err
	}

	return string(spec), nil
}

// Scan implements the database/sql Scanner interface for retrieving NotebookSpecs from the database.
func (s *NotebookSpec) Scan(value interface{}) error {
	var spec NotebookSpec
	if err := json.NewDecoder(strings.NewReader(value.(string))).Decode(&spec); err != nil {
		return err
	}

	*s = spec
	return nil
}

// NotebookService is the service contract for Notebooks.
type NotebookService interface {
	GetNotebook(ctx context.Context, id platform.ID) (*Notebook, error)
	CreateNotebook(ctx context.Context, create *NotebookReqBody) (*Notebook, error)
	UpdateNotebook(ctx context.Context, id platform.ID, update *NotebookReqBody) (*Notebook, error)
	DeleteNotebook(ctx context.Context, id platform.ID) error
	ListNotebooks(ctx context.Context, filter NotebookListFilter) ([]*Notebook, error)
}

// NotebookListFilter is a selection filter for listing notebooks.
type NotebookListFilter struct {
	OrgID platform.ID
	Page  Page
}

// Page contains pagination information
type Page struct {
	Offset int
	Limit  int
}

// Validate validates the Page
func (p Page) Validate() error {
	if p.Offset < 0 {
		return ErrOffsetNegative
	}
	if p.Limit <= 0 {
		return ErrLimitLTEZero
	}
	return nil
}

// NotebookReqBody contains fields for creating or updating notebooks.
type NotebookReqBody struct {
	OrgID platform.ID  `json:"orgID"`
	Name  string       `json:"name"`
	Spec  NotebookSpec `json:"spec"`
}

// Validate validates the creation object
func (n NotebookReqBody) Validate() error {
	if !n.OrgID.Valid() {
		return ErrOrgIDRequired
	}
	if n.Name == "" {
		return ErrNameRequired
	}
	if n.Spec == nil {
		return ErrSpecRequired
	}
	return nil
}