// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.

// Container resource
package resource

import (
	"context"
	"fmt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
_	"github.com/docker/docker/api/types/mount"
_	"github.com/docker/docker/api/types/network"
_	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/client"
	"gopkg.in/yaml.v3"
_	"log/slog"
	"net/url"
_	"os"
_	"os/exec"
	"path/filepath"
_	"strings"
	"encoding/json"
	"io"
	"gitea.rosskeen.house/rosskeen.house/machine"
)

type ContainerNetworkClient interface {
	ContainerClient
	NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
}

type ContainerNetwork struct {
	Id              string                `json:"ID,omitempty" yaml:"ID,omitempty"`
	Name            string                `json:"name" yaml:"name"`

	State string `yaml:"state"`

	apiClient ContainerNetworkClient
}

func init() {
	ResourceTypes.Register("container_network", func(u *url.URL) Resource {
		n := NewContainerNetwork(nil)
		n.Name = filepath.Join(u.Hostname(), u.Path)
		return n
	})
}

func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNetwork {
	var apiClient ContainerNetworkClient = containerClientApi
	if apiClient == nil {
		var err error
		apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
		if err != nil {
			panic(err)
		}
	}
	return &ContainerNetwork{
		apiClient: apiClient,
	}
}

func (n *ContainerNetwork) Clone() Resource {
	return &ContainerNetwork {
		Id: n.Id,
		Name: n.Name,
		State: n.State,
		apiClient: n.apiClient,
	}
}

func (n *ContainerNetwork) StateMachine() machine.Stater {
	return StorageMachine()
}

func (n *ContainerNetwork) URI() string {
	return fmt.Sprintf("container_network://%s", n.Name)
}

func (n *ContainerNetwork) SetURI(uri string) error {
	resourceUri, e := url.Parse(uri)
	if e == nil {
		if resourceUri.Scheme == n.Type() {
			n.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
		} else {
			e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, n.Type())
		}
	}
	return e
}

func (n *ContainerNetwork) JSON() ([]byte, error) {
	return json.Marshal(n)
}

func (n *ContainerNetwork) Validate() error {
	return fmt.Errorf("failed")
}

func (n *ContainerNetwork) Apply() error {
	ctx := context.Background()
	switch n.State {
	case "absent":
		return n.Delete(ctx)
	case "present":
		return n.Create(ctx)
	}
	return nil
}

func (n *ContainerNetwork) Load(r io.Reader) error {
	d := NewYAMLDecoder(r)
	return d.Decode(n)
}

func (n *ContainerNetwork) LoadDecl(yamlResourceDeclaration string) error {
	d := NewYAMLStringDecoder(yamlResourceDeclaration)
	return d.Decode(n)
}

func (n *ContainerNetwork) Create(ctx context.Context) error {
	networkResp, err := n.apiClient.NetworkCreate(ctx, n.Name, types.NetworkCreate{
		Driver: "bridge",
	})
	if err != nil {
		panic(err)
	}
	n.Id = networkResp.ID

	return nil
}

// produce yaml representation of any resource

func (n *ContainerNetwork) Read(ctx context.Context) ([]byte, error) {
	return yaml.Marshal(n)
}

func (n *ContainerNetwork) Delete(ctx context.Context) error {
	return nil
}

func (n *ContainerNetwork) Type() string { return "container_network" }

func (n *ContainerNetwork) ResolveId(ctx context.Context) string {
	filterArgs := filters.NewArgs()
	filterArgs.Add("name", "/"+n.Name)
	containers, err := n.apiClient.ContainerList(ctx, container.ListOptions{
		All:     true,
		Filters: filterArgs,
	})
	if err != nil {
		panic(fmt.Errorf("%w: %s %s", err, n.Type(), n.Name))
	}

	for _, container := range containers {
		for _, containerName := range container.Names {
			if containerName == n.Name {
				if n.Id == "" {
					n.Id = container.ID
				}
				return container.ID
			}
		}
	}
	return ""
}
