package libpod

import (
	"errors"
	"fmt"
	"time"

	"github.com/containers/podman/v4/libpod/define"
	"github.com/containers/storage"
	"github.com/sirupsen/logrus"
)

// StorageContainer represents a container present in c/storage but not in
// libpod.
type StorageContainer struct {
	ID              string
	Names           []string
	Image           string
	CreateTime      time.Time
	PresentInLibpod bool
}

// ListStorageContainers lists all containers visible to c/storage.
func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) {
	finalCtrs := []*StorageContainer{}

	ctrs, err := r.store.Containers()
	if err != nil {
		return nil, err
	}

	for _, ctr := range ctrs {
		storageCtr := new(StorageContainer)
		storageCtr.ID = ctr.ID
		storageCtr.Names = ctr.Names
		storageCtr.Image = ctr.ImageID
		storageCtr.CreateTime = ctr.Created

		// Look up if container is in state
		hasCtr, err := r.state.HasContainer(ctr.ID)
		if err != nil {
			return nil, fmt.Errorf("looking up container %s in state: %w", ctr.ID, err)
		}

		storageCtr.PresentInLibpod = hasCtr

		finalCtrs = append(finalCtrs, storageCtr)
	}

	return finalCtrs, nil
}

func (r *Runtime) StorageContainer(idOrName string) (*storage.Container, error) {
	return r.store.Container(idOrName)
}

// RemoveStorageContainer removes a container from c/storage.
// The container WILL NOT be removed if it exists in libpod.
// Accepts ID or full name of container.
// If force is set, the container will be unmounted first to ensure removal.
func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error {
	targetID, err := r.store.Lookup(idOrName)
	if err != nil {
		if errors.Is(err, storage.ErrLayerUnknown) {
			return fmt.Errorf("no container with ID or name %q found: %w", idOrName, define.ErrNoSuchCtr)
		}
		return fmt.Errorf("looking up container %q: %w", idOrName, err)
	}

	// Lookup returns an ID but it's not guaranteed to be a container ID.
	// So we can still error here.
	ctr, err := r.store.Container(targetID)
	if err != nil {
		if errors.Is(err, storage.ErrContainerUnknown) {
			return fmt.Errorf("%q does not refer to a container: %w", idOrName, define.ErrNoSuchCtr)
		}
		return fmt.Errorf("retrieving container %q: %w", idOrName, err)
	}

	// Error out if the container exists in libpod
	exists, err := r.state.HasContainer(ctr.ID)
	if err != nil {
		return err
	}
	if exists {
		return fmt.Errorf("refusing to remove %q as it exists in libpod as container %s: %w", idOrName, ctr.ID, define.ErrCtrExists)
	}

	// Error out if this is an image-backed volume
	allVols, err := r.state.AllVolumes()
	if err != nil {
		return err
	}
	for _, vol := range allVols {
		if vol.config.Driver == define.VolumeDriverImage && vol.config.StorageID == ctr.ID {
			return fmt.Errorf("refusing to remove %q as it exists in libpod as an image-backed volume %s: %w", idOrName, vol.Name(), define.ErrCtrExists)
		}
	}

	if !force {
		timesMounted, err := r.store.Mounted(ctr.ID)
		if err != nil {
			if errors.Is(err, storage.ErrContainerUnknown) {
				// Container was removed from under us.
				// It's gone, so don't bother erroring.
				logrus.Infof("Storage for container %s already removed", ctr.ID)
				return nil
			}
			logrus.Warnf("Checking if container %q is mounted, attempting to delete: %v", idOrName, err)
		}
		if timesMounted > 0 {
			return fmt.Errorf("container %q is mounted and cannot be removed without using force: %w", idOrName, define.ErrCtrStateInvalid)
		}
	} else if _, err := r.store.Unmount(ctr.ID, true); err != nil {
		if errors.Is(err, storage.ErrContainerUnknown) {
			// Container again gone, no error
			logrus.Infof("Storage for container %s already removed", ctr.ID)
			return nil
		}
		logrus.Warnf("Unmounting container %q while attempting to delete storage: %v", idOrName, err)
	}

	if err := r.store.DeleteContainer(ctr.ID); err != nil {
		if errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown) {
			// Container again gone, no error
			logrus.Infof("Storage for container %s already removed", ctr.ID)
			return nil
		}
		return fmt.Errorf("removing storage for container %q: %w", idOrName, err)
	}

	return nil
}
