package notion

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

// Page is a resource on the Notion platform. Its parent is either a workspace,
// another page, or a database.
// See: https://developers.notion.com/reference/page
type Page struct {
	ID             string     `json:"id"`
	CreatedTime    time.Time  `json:"created_time"`
	LastEditedTime time.Time  `json:"last_edited_time"`
	Parent         PageParent `json:"parent"`
	Archived       bool       `json:"archived"`

	// Properties differ between parent type.
	// See the `UnmarshalJSON` method.
	Properties interface{} `json:"properties"`
}

type PageParent struct {
	Type string `json:"type"`

	PageID     *string `json:"page_id,omitempty"`
	DatabaseID *string `json:"database_id,omitempty"`
}

// PageProperties are properties of a page whose parent is a page or a workspace.
type PageProperties struct {
	Title PageTitle `json:"title"`
}

type PageTitle struct {
	Title []RichText `json:"title"`
}

// DatabasePageProperties are properties of a page whose parent is a database.
type DatabasePageProperties map[string]DatabasePageProperty

type DatabasePageProperty struct {
	ID   string               `json:"id,omitempty"`
	Type DatabasePropertyType `json:"type"`

	RichText    []RichText        `json:"rich_text,omitempty"`
	Number      *NumberMetadata   `json:"number,omitempty"`
	Select      *SelectOptions    `json:"select,omitempty"`
	MultiSelect []SelectOptions   `json:"multi_select,omitempty"`
	Formula     *FormulaMetadata  `json:"formula,omitempty"`
	Relation    *RelationMetadata `json:"relation,omitempty"`
	Rollup      *RollupMetadata   `json:"rollup,omitempty"`
}

// CreatePageParams are the params used for creating a page.
type CreatePageParams struct {
	ParentType ParentType
	ParentID   string

	// Either DatabasePageProperties or Title must be not nil.
	DatabasePageProperties *DatabasePageProperties
	Title                  []RichText

	// Optionally, children blocks are added to the page.
	Children []Block
}

type UpdatePageParams struct {
	// Either DatabasePageProperties or Title must be not nil.
	DatabasePageProperties *DatabasePageProperties
	Title                  []RichText
}

type ParentType string

const (
	ParentTypeDatabase ParentType = "database_id"
	ParentTypePage     ParentType = "page_id"
)

func (p CreatePageParams) Validate() error {
	if p.ParentType == "" {
		return errors.New("parent type is required")
	}
	if p.ParentID == "" {
		return errors.New("parent ID is required")
	}
	if p.ParentType == ParentTypeDatabase && p.DatabasePageProperties == nil {
		return errors.New("database page properties is required when parent type is database")
	}
	if p.ParentType == ParentTypePage && p.Title == nil {
		return errors.New("title is required when parent type is page")
	}

	return nil
}

func (p CreatePageParams) MarshalJSON() ([]byte, error) {
	type CreatePageParamsDTO struct {
		Parent     PageParent  `json:"parent"`
		Properties interface{} `json:"properties"`
		Children   []Block     `json:"children,omitempty"`
	}

	var parent PageParent

	if p.DatabasePageProperties != nil {
		parent.Type = "database_id"
		parent.DatabaseID = StringPtr(p.ParentID)
	} else if p.Title != nil {
		parent.Type = "page_id"
		parent.PageID = StringPtr(p.ParentID)
	}

	dto := CreatePageParamsDTO{
		Parent:   parent,
		Children: p.Children,
	}

	if p.DatabasePageProperties != nil {
		dto.Properties = p.DatabasePageProperties
	} else if p.Title != nil {
		dto.Properties = PageTitle{
			Title: p.Title,
		}
	}

	return json.Marshal(dto)
}

// UnmarshalJSON implements json.Unmarshaler.
//
// Pages get a different Properties type based on the parent of the page.
// If parent type is `workspace` or `page_id`, PageProperties is used. Else if
// parent type is `database_id`, DatabasePageProperties is used.
func (p *Page) UnmarshalJSON(b []byte) error {
	type (
		PageAlias Page
		PageDTO   struct {
			PageAlias
			Properties json.RawMessage `json:"properties"`
		}
	)

	var dto PageDTO

	err := json.Unmarshal(b, &dto)
	if err != nil {
		return err
	}

	page := dto.PageAlias

	switch dto.Parent.Type {
	case "workspace":
		fallthrough
	case "page_id":
		var props PageProperties
		err := json.Unmarshal(dto.Properties, &props)
		if err != nil {
			return err
		}
		page.Properties = props
	case "database_id":
		var props DatabasePageProperties
		err := json.Unmarshal(dto.Properties, &props)
		if err != nil {
			return err
		}
		page.Properties = props
	default:
		return fmt.Errorf("unknown page parent type %q", dto.Parent.Type)
	}

	*p = Page(page)

	return nil
}

func (p UpdatePageParams) Validate() error {
	if p.DatabasePageProperties == nil && p.Title == nil {
		return errors.New("either database page properties or title is required")
	}
	return nil
}

func (p UpdatePageParams) MarshalJSON() ([]byte, error) {
	type UpdatePageParamsDTO struct {
		Properties interface{} `json:"properties"`
	}

	var dto UpdatePageParamsDTO

	if p.DatabasePageProperties != nil {
		dto.Properties = p.DatabasePageProperties
	} else if p.Title != nil {
		dto.Properties = PageTitle{
			Title: p.Title,
		}
	}

	return json.Marshal(dto)
}