package main

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strconv"

	"go.yaml.in/yaml/v4"

	"github.com/lxc/incus/v7/shared/api"
	"github.com/lxc/incus/v7/shared/logger"
	"github.com/lxc/incus/v7/shared/util"
)

func templatesApply(path string) ([]string, error) {
	metaName := filepath.Join(path, "metadata.yaml")
	if !util.PathExists(metaName) {
		return nil, nil
	}

	// Parse the metadata.
	content, err := os.ReadFile(metaName)
	if err != nil {
		return nil, fmt.Errorf("Failed to read metadata: %w", err)
	}

	metadata := &api.ImageMetadata{}
	err = yaml.Load(content, metadata)
	if err != nil {
		return nil, fmt.Errorf("Could not parse metadata.yaml: %w", err)
	}

	// Go through the files and copy them into place.
	files := []string{}
	for tplPath, tpl := range metadata.Templates {
		err = func(tplPath string, tpl *api.ImageMetadataTemplate) error {
			filePath := filepath.Join(path, fmt.Sprintf("%s.out", tpl.Template))

			if !util.PathExists(filePath) {
				return nil
			}

			var w *os.File
			if util.PathExists(tplPath) {
				if tpl.CreateOnly {
					return nil
				}

				// Open the existing file.
				w, err = os.Create(tplPath)
				if err != nil {
					return fmt.Errorf("Failed to create template file: %w", err)
				}
			} else {
				// UID and GID
				fileUID := int64(0)
				fileGID := int64(0)

				if tpl.UID != "" {
					id, err := strconv.ParseInt(tpl.UID, 10, 64)
					if err != nil {
						return fmt.Errorf("Bad file UID %q for %q: %w", tpl.UID, tplPath, err)
					}

					fileUID = id
				}

				if tpl.GID != "" {
					id, err := strconv.ParseInt(tpl.GID, 10, 64)
					if err != nil {
						return fmt.Errorf("Bad file GID %q for %q: %w", tpl.GID, tplPath, err)
					}

					fileGID = id
				}

				// Mode
				fileMode := fs.FileMode(0o644)
				if tpl.Mode != "" {
					if len(tpl.Mode) == 3 {
						tpl.Mode = fmt.Sprintf("0%s", tpl.Mode)
					}

					mode, err := strconv.ParseInt(tpl.Mode, 0, 0)
					if err != nil {
						return fmt.Errorf("Bad mode %q for %q: %w", tpl.Mode, tplPath, err)
					}

					fileMode = os.FileMode(mode) & os.ModePerm
				}

				// Create the directories leading to the file.
				err := os.MkdirAll(filepath.Dir(tplPath), 0o755)
				if err != nil {
					return err
				}

				// Create the file itself.
				w, err = os.Create(tplPath)
				if err != nil {
					return err
				}

				// Fix ownership.
				err = w.Chown(int(fileUID), int(fileGID))
				if err != nil {
					return err
				}

				// Fix mode.
				err = w.Chmod(fileMode)
				if err != nil {
					return err
				}
			}
			defer logger.WarnOnErrorExcept(w.Close, []error{os.ErrClosed}, "Failed to close file")

			// Do the copy.
			src, err := os.Open(filePath)
			if err != nil {
				return err
			}

			defer logger.WarnOnError(src.Close, "Failed to close source file")

			_, err = util.SafeCopy(w, src)
			if err != nil {
				return err
			}

			err = w.Close()
			if err != nil {
				return err
			}

			files = append(files, tplPath)

			return nil
		}(tplPath, tpl)
		if err != nil {
			return nil, err
		}
	}

	return files, nil
}
