package logs

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/signal"
	"sync"

	"coopcloud.tech/abra/pkg/i18n"
	"github.com/docker/docker/api/types"
	containerTypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	dockerClient "github.com/docker/docker/client"
)

type TailOpts struct {
	AppName  string
	Services []string
	StdErr   bool
	Since    string
	Buffer   *[]string
	ToBuffer bool
	Filters  filters.Args
}

// TailLogs gathers logs for the given app with optional service names to be
// filtered on. These logs can be printed to os.Stdout or gathered to a buffer.
func TailLogs(
	cl *dockerClient.Client,
	opts TailOpts,
) error {
	sigIntCh := make(chan os.Signal, 1)
	signal.Notify(sigIntCh, os.Interrupt)
	defer signal.Stop(sigIntCh)

	services, err := cl.ServiceList(
		context.Background(),
		types.ServiceListOptions{Filters: opts.Filters},
	)
	if err != nil {
		return err
	}

	errCh := make(chan error)
	waitCh := make(chan struct{})

	go func() {
		var wg sync.WaitGroup
		for _, service := range services {
			wg.Add(1)
			go func(serviceID string) {
				tail := "50"
				if opts.ToBuffer {
					// NOTE(d1): more logs from before deployment when analysing via file
					tail = "150"
				}

				logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
					ShowStderr: true,
					ShowStdout: !opts.StdErr,
					Since:      opts.Since,
					Until:      "",
					Timestamps: true,
					Follow:     true,
					Tail:       tail,
					Details:    false,
				})

				if err == nil {
					defer logs.Close()
					if opts.ToBuffer {
						buf := bufio.NewScanner(logs)
						for buf.Scan() {
							line := fmt.Sprintf("%s: %s", service.Spec.Name, buf.Text())
							*opts.Buffer = append(*opts.Buffer, line)
						}
						logs.Close()
						return
					}

					if _, err = io.Copy(os.Stdout, logs); err != nil && err != io.EOF {
						errCh <- errors.New(i18n.G("tailLogs: unable to copy buffer: %s", err))
					}
				}
			}(service.ID)
		}

		wg.Wait()
		close(waitCh)
	}()

	select {
	case <-waitCh:
		return nil
	case <-sigIntCh:
		return nil
	case err := <-errCh:
		return err
	}

	return nil
}
