// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package orchestrator

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/google/android-cuttlefish/frontend/src/liboperator/operator"
	"github.com/google/uuid"
)

type ImageDirectoriesManager interface {
	// Create an empty image directory.
	CreateImageDirectory() (string, error)
	// List all image directories.
	ListImageDirectories() ([]string, error)
	// Update image directory with creating or modifying symlinks of all files
	// under specified directory.
	UpdateImageDirectory(imageDirName, dir string) error
	// Delete the specified image directory.
	DeleteImageDirectory(imageDirName string) error
}

// Options for creating instances of ImageDirectoriesManager implementations.
type ImageDirectoriesManagerOpts struct {
	// Root directory for storing image directories.
	RootDir string
}

// An implementation of the ImageDirectoriesManager interface.
type ImageDirectoriesManagerImpl struct {
	ImageDirectoriesManagerOpts
}

func NewImageDirectoriesManagerImpl(opts ImageDirectoriesManagerOpts) *ImageDirectoriesManagerImpl {
	return &ImageDirectoriesManagerImpl{ImageDirectoriesManagerOpts: opts}
}

func (m *ImageDirectoriesManagerImpl) CreateImageDirectory() (string, error) {
	if err := createDir(m.RootDir); err != nil {
		return "", fmt.Errorf("failed to create root directory for storing image directories: %w", err)
	}
	dirname := uuid.New().String()
	if err := createDir(filepath.Join(m.RootDir, dirname)); err != nil {
		return "", fmt.Errorf("failed to create an image directory: %w", err)
	}
	return dirname, nil
}

func (m *ImageDirectoriesManagerImpl) ListImageDirectories() ([]string, error) {
	imageDirs := []string{}
	if exists, err := dirExists(m.RootDir); err != nil {
		return nil, fmt.Errorf("failed to check existence of directory: %w", err)
	} else if !exists {
		return imageDirs, nil
	}
	entries, err := ioutil.ReadDir(m.RootDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read directory: %w", err)
	}
	for _, entry := range entries {
		if !entry.IsDir() {
			return nil, fmt.Errorf("invalid image directory: %q", entry.Name())
		}
		imageDirs = append(imageDirs, entry.Name())
	}
	return imageDirs, nil
}

func (m *ImageDirectoriesManagerImpl) UpdateImageDirectory(imageDirName, dir string) error {
	if exists, err := dirExists(dir); err != nil {
		return fmt.Errorf("failed to check existence of directory: %w", err)
	} else if !exists {
		return operator.NewNotFoundError(fmt.Sprintf("directory(dir:%q) not found", dir), nil)
	}
	imageDir := filepath.Join(m.RootDir, imageDirName)
	if exists, err := dirExists(imageDir); err != nil {
		return fmt.Errorf("failed to check existence of directory: %w", err)
	} else if !exists {
		return operator.NewNotFoundError(fmt.Sprintf("image directory(dir:%q) not found", imageDirName), nil)
	}
	entries, err := ioutil.ReadDir(dir)
	if err != nil {
		return fmt.Errorf("failed to read directory: %w", err)
	}
	for _, entry := range entries {
		symlink := filepath.Join(imageDir, entry.Name())
		if exists, err := fileExist(symlink); err != nil {
			return fmt.Errorf("failed to check existence of file: %w", err)
		} else if exists {
			if err := os.Remove(symlink); err != nil {
				return fmt.Errorf("failed to remove previous symlink: %w", err)
			}
		}
		if err := os.Symlink(filepath.Join(dir, entry.Name()), symlink); err != nil {
			return fmt.Errorf("failed to create symlink: %w", err)
		}
	}
	return nil
}

func (m *ImageDirectoriesManagerImpl) DeleteImageDirectory(imageDirName string) error {
	imageDir := filepath.Join(m.RootDir, imageDirName)
	if exists, err := dirExists(imageDir); err != nil {
		return fmt.Errorf("failed to check existence of directory: %w", err)
	} else if !exists {
		return operator.NewNotFoundError(fmt.Sprintf("image directory(dir:%q) not found", imageDirName), nil)
	}
	if err := os.RemoveAll(imageDir); err != nil {
		return fmt.Errorf("failed to remove image directory: %w", err)
	}
	return nil
}
