package client

import (
	"context"
	"net/http"
	"net/url"
	"time"

	"github.com/moby/moby/api/types"
	"github.com/moby/moby/api/types/events"
	"github.com/moby/moby/client/internal"
	"github.com/moby/moby/client/internal/timestamp"
)

// EventsListOptions holds parameters to filter events with.
type EventsListOptions struct {
	Since   string
	Until   string
	Filters Filters
}

// EventsResult holds the result of an Events query.
type EventsResult struct {
	Messages <-chan events.Message
	Err      <-chan error
}

// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an [io.EOF] error is
// sent over the error channel. If an error is sent, all processing is stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *Client) Events(ctx context.Context, options EventsListOptions) EventsResult {
	messages := make(chan events.Message)
	errs := make(chan error, 1)

	started := make(chan struct{})
	go func() {
		defer close(errs)

		query, err := buildEventsQueryParams(options)
		if err != nil {
			close(started)
			errs <- err
			return
		}

		headers := http.Header{}
		headers.Add("Accept", types.MediaTypeJSONLines) // Implicit q=1.0; in case server doesn't parse correctly.
		headers.Add("Accept", types.MediaTypeNDJSON+";q=0.9")
		headers.Add("Accept", types.MediaTypeJSONSequence+";q=0.5")
		resp, err := cli.get(ctx, "/events", query, headers)
		if err != nil {
			close(started)
			errs <- err
			return
		}
		defer resp.Body.Close()

		contentType := resp.Header.Get("Content-Type")
		decoder := internal.NewJSONStreamDecoder(resp.Body, contentType)

		close(started)
		for {
			select {
			case <-ctx.Done():
				errs <- ctx.Err()
				return
			default:
				var event events.Message
				if err := decoder(&event); err != nil {
					errs <- err
					return
				}

				select {
				case messages <- event:
				case <-ctx.Done():
					errs <- ctx.Err()
					return
				}
			}
		}
	}()
	<-started

	return EventsResult{
		Messages: messages,
		Err:      errs,
	}
}

func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
	query := url.Values{}
	ref := time.Now()

	if options.Since != "" {
		ts, err := timestamp.GetTimestamp(options.Since, ref)
		if err != nil {
			return nil, err
		}
		query.Set("since", ts)
	}

	if options.Until != "" {
		ts, err := timestamp.GetTimestamp(options.Until, ref)
		if err != nil {
			return nil, err
		}
		query.Set("until", ts)
	}

	options.Filters.updateURLValues(query)

	return query, nil
}
