package rest

import (
	"context"

	"github.com/titpetric/factory/resputil"

	"github.com/cortezaproject/corteza-server/compose/rest/request"
	"github.com/cortezaproject/corteza-server/compose/service"
	"github.com/cortezaproject/corteza-server/compose/service/event"
	"github.com/cortezaproject/corteza-server/compose/types"
	"github.com/cortezaproject/corteza-server/pkg/corredor"
	"github.com/cortezaproject/corteza-server/pkg/payload"
	"github.com/cortezaproject/corteza-server/pkg/rh"
)

type (
	pagePayload struct {
		*types.Page

		Children []*pagePayload `json:"children,omitempty"`

		CanGrant      bool `json:"canGrant"`
		CanUpdatePage bool `json:"canUpdatePage"`
		CanDeletePage bool `json:"canDeletePage"`
	}

	pageSetPayload struct {
		Filter types.PageFilter `json:"filter"`
		Set    []*pagePayload   `json:"set"`
	}

	Page struct {
		page       service.PageService
		namespace  service.NamespaceService
		attachment service.AttachmentService
		ac         pageAccessController
	}

	pageAccessController interface {
		CanGrant(context.Context) bool

		CanUpdatePage(context.Context, *types.Page) bool
		CanDeletePage(context.Context, *types.Page) bool
	}
)

func (Page) New() *Page {
	return &Page{
		page:       service.DefaultPage,
		namespace:  service.DefaultNamespace,
		attachment: service.DefaultAttachment,
		ac:         service.DefaultAccessControl,
	}
}

func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, error) {
	f := types.PageFilter{
		NamespaceID: r.NamespaceID,
		ParentID:    r.SelfID,

		Handle: r.Handle,
		Query:  r.Query,

		Sort: r.Sort,

		PageFilter: rh.Paging(r.Page, r.PerPage),
	}

	set, filter, err := ctrl.page.With(ctx).Find(f)
	return ctrl.makeFilterPayload(ctx, set, filter, err)
}

func (ctrl *Page) Tree(ctx context.Context, r *request.PageTree) (interface{}, error) {
	tree, err := ctrl.page.With(ctx).Tree(r.NamespaceID)
	return ctrl.makeTreePayload(ctx, tree, err)
}

func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{}, error) {
	var (
		err error
		mod = &types.Page{
			NamespaceID: r.NamespaceID,
			SelfID:      r.SelfID,
			ModuleID:    r.ModuleID,
			Title:       r.Title,
			Handle:      r.Handle,
			Description: r.Description,
			Visible:     r.Visible,
		}
	)

	if len(r.Blocks) > 2 {
		if err = r.Blocks.Unmarshal(&mod.Blocks); err != nil {
			return nil, err
		}
	}
	mod, err = ctrl.page.With(ctx).Create(mod)
	return ctrl.makePayload(ctx, mod, err)
}

func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) {
	mod, err := ctrl.page.With(ctx).FindByID(r.NamespaceID, r.PageID)
	return ctrl.makePayload(ctx, mod, err)

}

func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) {
	return resputil.OK(), ctrl.page.With(ctx).Reorder(r.NamespaceID, r.SelfID, payload.ParseUInt64s(r.PageIDs))
}

func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{}, error) {
	var (
		err error
		mod = &types.Page{
			NamespaceID: r.NamespaceID,
			ID:          r.PageID,
			SelfID:      r.SelfID,
			ModuleID:    r.ModuleID,
			Title:       r.Title,
			Handle:      r.Handle,
			Description: r.Description,
			Visible:     r.Visible,
			Weight:      r.Weight,
		}
	)

	if len(r.Blocks) > 2 {
		if err = r.Blocks.Unmarshal(&mod.Blocks); err != nil {
			return nil, err
		}
	}

	mod, err = ctrl.page.With(ctx).Update(mod)
	return ctrl.makePayload(ctx, mod, err)
}

func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) {
	return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.NamespaceID, r.PageID)
}

func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{}, error) {
	file, err := r.Upload.Open()
	if err != nil {
		return nil, err
	}

	defer file.Close()

	a, err := ctrl.attachment.With(ctx).CreatePageAttachment(
		r.NamespaceID,
		r.Upload.Filename,
		r.Upload.Size,
		file,
		r.PageID,
	)

	return makeAttachmentPayload(ctx, a, err)
}

func (ctrl *Page) TriggerScript(ctx context.Context, r *request.PageTriggerScript) (rsp interface{}, err error) {
	var (
		page      *types.Page
		namespace *types.Namespace
	)

	if page, err = ctrl.page.FindByID(r.NamespaceID, r.PageID); err != nil {
		return
	}
	if namespace, err = ctrl.namespace.With(ctx).FindByID(r.NamespaceID); err != nil {
		return
	}

	// @todo implement same behaviour as we have on record - page+oldPage
	err = corredor.Service().Exec(ctx, r.Script, event.PageOnManual(page, page, namespace))
	return ctrl.makePayload(ctx, page, err)
}

func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pagePayload, error) {
	if err != nil || c == nil {
		return nil, err
	}

	return &pagePayload{
		Page: c,

		CanGrant: ctrl.ac.CanGrant(ctx),

		CanUpdatePage: ctrl.ac.CanUpdatePage(ctx, c),
		CanDeletePage: ctrl.ac.CanDeletePage(ctx, c),
	}, nil
}

func (ctrl Page) makeTreePayload(ctx context.Context, pp types.PageSet, err error) ([]*pagePayload, error) {
	if err != nil {
		return nil, err
	}

	set := make([]*pagePayload, len(pp))

	for i := range pp {
		set[i], err = ctrl.makePayload(ctx, pp[i], nil)
		if err != nil {
			return nil, err
		}

		if len(pp[i].Children) > 0 {
			set[i].Children, err = ctrl.makeTreePayload(ctx, pp[i].Children, nil)
			if err != nil {
				return nil, err
			}
		}
	}

	return set, nil
}

func (ctrl Page) makeFilterPayload(ctx context.Context, nn types.PageSet, f types.PageFilter, err error) (*pageSetPayload, error) {
	if err != nil {
		return nil, err
	}

	modp := &pageSetPayload{Filter: f, Set: make([]*pagePayload, len(nn))}

	for i := range nn {
		modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
	}

	return modp, nil
}