package client

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"time"

	"github.com/moby/moby/client/internal/timestamp"
)

// ContainerLogsOptions holds parameters to filter logs with.
type ContainerLogsOptions struct {
	ShowStdout bool
	ShowStderr bool
	Since      string
	Until      string
	Timestamps bool
	Follow     bool
	Tail       string
	Details    bool
}

// ContainerLogsResult is the result of a container logs operation.
type ContainerLogsResult interface {
	io.ReadCloser
}

// ContainerLogs returns the logs generated by a container in an [io.ReadCloser].
// It's up to the caller to close the stream.
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
//
// The stream format on the response uses one of two formats:
//
//   - If the container is using a TTY, there is only a single stream (stdout)
//     and data is copied directly from the container output stream, no extra
//     multiplexing or headers.
//   - If the container is *not* using a TTY, streams for stdout and stderr are
//     multiplexed.
//
// The format of the multiplexed stream is defined in the [stdcopy] package,
// and as follows:
//
//	[8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT}
//
// STREAM_TYPE can be 1 for [Stdout] and 2 for [Stderr]. Refer to [stdcopy.StdType]
// for details. SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded
// as big endian, this is the size of OUTPUT. You can use [stdcopy.StdCopy]
// to demultiplex this stream.
//
// [stdcopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy
// [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy
// [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType
// [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout
// [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr
func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options ContainerLogsOptions) (ContainerLogsResult, error) {
	containerID, err := trimID("container", containerID)
	if err != nil {
		return nil, err
	}

	query := url.Values{}
	if options.ShowStdout {
		query.Set("stdout", "1")
	}

	if options.ShowStderr {
		query.Set("stderr", "1")
	}

	if options.Since != "" {
		ts, err := timestamp.GetTimestamp(options.Since, time.Now())
		if err != nil {
			return nil, fmt.Errorf(`invalid value for "since": %w`, err)
		}
		query.Set("since", ts)
	}

	if options.Until != "" {
		ts, err := timestamp.GetTimestamp(options.Until, time.Now())
		if err != nil {
			return nil, fmt.Errorf(`invalid value for "until": %w`, err)
		}
		query.Set("until", ts)
	}

	if options.Timestamps {
		query.Set("timestamps", "1")
	}

	if options.Details {
		query.Set("details", "1")
	}

	if options.Follow {
		query.Set("follow", "1")
	}

	switch options.Tail {
	case "", "all":
		// don't send option; default is to show all logs.
		//
		// The default on the daemon-side is to show all logs; account for
		// some special values. The CLI may set a magic "all" value that's
		// used as default.
		//
		// Given that the default is to show all logs, we can ignore these
		// values, and don't send "tail".
		//
		// see https://github.com/moby/moby/blob/0df791cb72b568eeadba2267fe9a5040d12b0487/daemon/logs.go#L75-L78
		// see https://github.com/moby/moby/blob/4d20b6fe56dfb2b06f4a5dd1f32913215a9c317b/daemon/cluster/services.go#L425-L449
	default:
		query.Set("tail", options.Tail)
	}

	resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
	if err != nil {
		return nil, err
	}
	return &containerLogsResult{
		ReadCloser: newCancelReadCloser(ctx, resp.Body),
	}, nil
}

type containerLogsResult struct {
	io.ReadCloser
}

var (
	_ io.ReadCloser       = (*containerLogsResult)(nil)
	_ ContainerLogsResult = (*containerLogsResult)(nil)
)
