package http

import (
	"context"
	"hash/fnv"

	"github.com/PuerkitoBio/goquery"

	"github.com/MontFerret/ferret/pkg/drivers"
	"github.com/MontFerret/ferret/pkg/drivers/common"
	"github.com/MontFerret/ferret/pkg/runtime/core"
	"github.com/MontFerret/ferret/pkg/runtime/values"
)

type HTMLPage struct {
	document *HTMLDocument
	cookies  drivers.HTTPCookies
	frames   *values.Array
	response *drivers.HTTPResponse
}

func NewHTMLPage(
	qdoc *goquery.Document,
	url string,
	response *drivers.HTTPResponse,
	cookies drivers.HTTPCookies,
) (*HTMLPage, error) {
	doc, err := NewRootHTMLDocument(qdoc, url)

	if err != nil {
		return nil, err
	}

	p := new(HTMLPage)
	p.document = doc
	p.cookies = cookies
	p.frames = nil
	p.response = response

	return p, nil
}

func (p *HTMLPage) MarshalJSON() ([]byte, error) {
	return p.document.MarshalJSON()
}

func (p *HTMLPage) Type() core.Type {
	return drivers.HTMLPageType
}

func (p *HTMLPage) String() string {
	return p.document.GetURL().String()
}

func (p *HTMLPage) Compare(other core.Value) int64 {
	tc := drivers.Compare(p.Type(), other.Type())

	if tc != 0 {
		return tc
	}

	httpPage, ok := other.(*HTMLPage)

	if !ok {
		return 1
	}

	return p.document.GetURL().Compare(httpPage.GetURL())
}

func (p *HTMLPage) Unwrap() interface{} {
	return p.document
}

func (p *HTMLPage) Hash() uint64 {
	h := fnv.New64a()

	h.Write([]byte("HTTP"))
	h.Write([]byte(p.Type().String()))
	h.Write([]byte(":"))
	h.Write([]byte(p.document.GetURL()))

	return h.Sum64()
}

func (p *HTMLPage) Copy() core.Value {
	cookies := make(drivers.HTTPCookies)

	for k, v := range p.cookies {
		cookies[k] = v
	}

	page, err := NewHTMLPage(
		p.document.doc,
		p.document.GetURL().String(),
		p.response,
		cookies,
	)

	if err != nil {
		return values.None
	}

	return page
}

func (p *HTMLPage) Iterate(ctx context.Context) (core.Iterator, error) {
	return p.document.Iterate(ctx)
}

func (p *HTMLPage) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
	return common.GetInPage(ctx, p, path)
}

func (p *HTMLPage) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
	return common.SetInPage(ctx, p, path, value)
}

func (p *HTMLPage) Length() values.Int {
	return p.document.Length()
}

func (p *HTMLPage) Close() error {
	return nil
}

func (p *HTMLPage) IsClosed() values.Boolean {
	return values.True
}

func (p *HTMLPage) GetURL() values.String {
	return p.document.GetURL()
}

func (p *HTMLPage) GetMainFrame() drivers.HTMLDocument {
	return p.document
}

func (p *HTMLPage) GetFrames(ctx context.Context) (*values.Array, error) {
	if p.frames == nil {
		arr := values.NewArray(10)

		err := common.CollectFrames(ctx, arr, p.document)

		if err != nil {
			return values.NewArray(0), err
		}

		p.frames = arr
	}

	return p.frames, nil
}

func (p *HTMLPage) GetFrame(ctx context.Context, idx values.Int) (core.Value, error) {
	if p.frames == nil {
		arr := values.NewArray(10)

		err := common.CollectFrames(ctx, arr, p.document)

		if err != nil {
			return values.None, err
		}

		p.frames = arr
	}

	return p.frames.Get(idx), nil
}

func (p *HTMLPage) GetCookies(_ context.Context) (*values.Array, error) {
	if p.cookies == nil {
		return values.NewArray(0), nil
	}

	arr := values.NewArray(len(p.cookies))

	for _, c := range p.cookies {
		arr.Push(c)
	}

	return arr, nil
}

func (p *HTMLPage) GetResponse(_ context.Context) (*drivers.HTTPResponse, error) {
	return p.response, nil
}

func (p *HTMLPage) SetCookies(_ context.Context, _ ...drivers.HTTPCookie) error {
	return core.ErrNotSupported
}

func (p *HTMLPage) DeleteCookies(_ context.Context, _ ...drivers.HTTPCookie) error {
	return core.ErrNotSupported
}

func (p *HTMLPage) PrintToPDF(_ context.Context, _ drivers.PDFParams) (values.Binary, error) {
	return nil, core.ErrNotSupported
}

func (p *HTMLPage) CaptureScreenshot(_ context.Context, _ drivers.ScreenshotParams) (values.Binary, error) {
	return nil, core.ErrNotSupported
}

func (p *HTMLPage) WaitForNavigation(_ context.Context) error {
	return core.ErrNotSupported
}

func (p *HTMLPage) Navigate(_ context.Context, _ values.String) error {
	return core.ErrNotSupported
}

func (p *HTMLPage) NavigateBack(_ context.Context, _ values.Int) (values.Boolean, error) {
	return false, core.ErrNotSupported
}

func (p *HTMLPage) NavigateForward(_ context.Context, _ values.Int) (values.Boolean, error) {
	return false, core.ErrNotSupported
}
