// Copyright 2022 Linka Cloud  All rights reserved.
//
// 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
//
//   http://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 d2vm

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/uuid"
	"github.com/sirupsen/logrus"
	"github.com/svenwiltink/sparsecat"

	"go.linka.cloud/d2vm/pkg/docker"
)

func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
	o := &convertOptions{}
	for _, opt := range opts {
		opt(o)
	}
	imgUUID := uuid.New().String()
	tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID)
	if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	logrus.Infof("inspecting image %s", img)
	r, err := FetchDockerImageOSRelease(ctx, img, tmpPath)
	if err != nil {
		return err
	}

	if o.luksPassword != "" && !r.SupportsLUKS() {
		return fmt.Errorf("luks is not supported for %s %s", r.Name, r.Version)
	}

	if !o.raw {
		d, err := NewDockerfile(r, img, o.password, o.networkManager, o.luksPassword != "")
		if err != nil {
			return err
		}
		logrus.Infof("docker image based on %s %s", d.Release.Name, d.Release.Version)
		p := filepath.Join(tmpPath, docker.FormatImgName(img))
		dir := filepath.Dir(p)
		f, err := os.Create(p)
		if err != nil {
			return err
		}
		defer f.Close()
		if err := d.Render(f); err != nil {
			return err
		}
		logrus.Infof("building kernel enabled image")
		if err := docker.Build(ctx, imgUUID, p, dir); err != nil {
			return err
		}
		if !o.keepCache {
			defer docker.Remove(ctx, imgUUID)
		}
	} else {
		// for raw images, we just tag the image with the uuid
		if err := docker.Tag(ctx, img, imgUUID); err != nil {
			return err
		}
		if !o.keepCache {
			defer docker.Remove(ctx, imgUUID)
		}
	}

	logrus.Infof("creating vm image")
	format := strings.TrimPrefix(filepath.Ext(o.output), ".")
	if format == "" {
		format = "raw"
	}
	b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootSize, o.luksPassword)
	if err != nil {
		return err
	}
	defer b.Close()
	if err := b.Build(ctx); err != nil {
		return err
	}
	if err := os.RemoveAll(o.output); err != nil {
		return err
	}
	if err := MoveFile(filepath.Join(tmpPath, "disk0."+format), o.output); err != nil {
		return err
	}
	return nil
}

func MoveFile(sourcePath, destPath string) error {
	inputFile, err := os.Open(sourcePath)
	if err != nil {
		return fmt.Errorf("failed to open source file: %s", err)
	}
	outputFile, err := os.Create(destPath)
	if err != nil {
		inputFile.Close()
		return fmt.Errorf("failed to open dest file: %s", err)
	}
	defer outputFile.Close()
	_, err = sparsecat.NewDecoder(sparsecat.NewEncoder(inputFile)).WriteTo(outputFile)
	inputFile.Close()
	if err != nil {
		return fmt.Errorf("failed to write to output file: %s", err)
	}
	// The copy was successful, so now delete the original file
	err = os.Remove(sourcePath)
	if err != nil {
		return fmt.Errorf("failed to remove original file: %s", err)
	}
	return nil
}
