package provider

import (
	"context"

	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
	"github.com/yusing/godoxy/internal/docker"
	idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
	"github.com/yusing/godoxy/internal/types"
	"github.com/yusing/godoxy/internal/watcher"
	gperr "github.com/yusing/goutils/errs"
)

type DockerProvider struct {
	client      *docker.SharedClient
	watcher     watcher.DockerWatcher
	containerID string
}

var startOptions = client.ContainerStartOptions{}

func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string) (idlewatcher.Provider, error) {
	client, err := docker.NewClient(dockerCfg)
	if err != nil {
		return nil, err
	}
	return &DockerProvider{
		client:      client,
		watcher:     watcher.NewDockerWatcher(dockerCfg),
		containerID: containerID,
	}, nil
}

func (p *DockerProvider) ContainerPause(ctx context.Context) error {
	_, err := p.client.ContainerPause(ctx, p.containerID, client.ContainerPauseOptions{})
	return err
}

func (p *DockerProvider) ContainerUnpause(ctx context.Context) error {
	_, err := p.client.ContainerUnpause(ctx, p.containerID, client.ContainerUnpauseOptions{})
	return err
}

func (p *DockerProvider) ContainerStart(ctx context.Context) error {
	_, err := p.client.ContainerStart(ctx, p.containerID, startOptions)
	return err
}

func (p *DockerProvider) ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error {
	_, err := p.client.ContainerStop(ctx, p.containerID, client.ContainerStopOptions{
		Signal:  string(signal),
		Timeout: &timeout,
	})
	return err
}

func (p *DockerProvider) ContainerKill(ctx context.Context, signal types.ContainerSignal) error {
	_, err := p.client.ContainerKill(ctx, p.containerID, client.ContainerKillOptions{
		Signal: string(signal),
	})
	return err
}

func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.ContainerStatus, error) {
	status, err := p.client.ContainerInspect(ctx, p.containerID, client.ContainerInspectOptions{})
	if err != nil {
		return idlewatcher.ContainerStatusError, err
	}
	switch status.Container.State.Status {
	case container.StateRunning:
		return idlewatcher.ContainerStatusRunning, nil
	case container.StateExited, container.StateDead, container.StateRestarting:
		return idlewatcher.ContainerStatusStopped, nil
	case container.StatePaused:
		return idlewatcher.ContainerStatusPaused, nil
	}
	return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status.Container.State.Status))
}

func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan gperr.Error) {
	return p.watcher.EventsWithOptions(ctx, watcher.DockerListOptions{
		Filters: watcher.NewDockerFilters(
			watcher.DockerFilterContainer,
			watcher.DockerFilterContainerNameID(p.containerID),
			watcher.DockerFilterStart,
			watcher.DockerFilterStop,
			watcher.DockerFilterDie,
			watcher.DockerFilterKill,
			watcher.DockerFilterDestroy,
			watcher.DockerFilterPause,
			watcher.DockerFilterUnpause,
		),
	})
}

func (p *DockerProvider) Close() {
	p.client.Close()
}
