package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"

	homedir "github.com/mitchellh/go-homedir"

	"github.com/cloudflare/cloudflared/config"
)

type ServiceTemplate struct {
	Path     string
	Content  string
	FileMode os.FileMode
}

type ServiceTemplateArgs struct {
	Path      string
	ExtraArgs []string
}

func (st *ServiceTemplate) ResolvePath() (string, error) {
	resolvedPath, err := homedir.Expand(st.Path)
	if err != nil {
		return "", fmt.Errorf("error resolving path %s: %v", st.Path, err)
	}
	return resolvedPath, nil
}

func (st *ServiceTemplate) Generate(args *ServiceTemplateArgs) error {
	tmpl, err := template.New(st.Path).Parse(st.Content)
	if err != nil {
		return fmt.Errorf("error generating %s template: %v", st.Path, err)
	}
	resolvedPath, err := st.ResolvePath()
	if err != nil {
		return err
	}
	if _, err = os.Stat(resolvedPath); err == nil {
		return errors.New(serviceAlreadyExistsWarn(resolvedPath))
	}

	var buffer bytes.Buffer
	err = tmpl.Execute(&buffer, args)
	if err != nil {
		return fmt.Errorf("error generating %s: %v", st.Path, err)
	}
	fileMode := os.FileMode(0o644)
	if st.FileMode != 0 {
		fileMode = st.FileMode
	}

	plistFolder := filepath.Dir(resolvedPath)
	err = os.MkdirAll(plistFolder, 0o755)
	if err != nil {
		return fmt.Errorf("error creating %s: %v", plistFolder, err)
	}

	err = os.WriteFile(resolvedPath, buffer.Bytes(), fileMode)
	if err != nil {
		return fmt.Errorf("error writing %s: %v", resolvedPath, err)
	}
	return nil
}

func (st *ServiceTemplate) Remove() error {
	resolvedPath, err := st.ResolvePath()
	if err != nil {
		return err
	}
	err = os.Remove(resolvedPath)
	if err != nil {
		return fmt.Errorf("error deleting %s: %v", resolvedPath, err)
	}
	return nil
}

func serviceAlreadyExistsWarn(service string) string {
	return fmt.Sprintf("cloudflared service is already installed at %s; if you are running a cloudflared tunnel, you "+
		"can point it to multiple origins, avoiding the need to run more than one cloudflared service in the "+
		"same machine; otherwise if you are really sure, you can do `cloudflared service uninstall` to clean "+
		"up the existing service and then try again this command",
		service,
	)
}

func runCommand(command string, args ...string) error {
	cmd := exec.Command(command, args...)
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return fmt.Errorf("error getting stderr pipe: %v", err)
	}
	err = cmd.Start()
	if err != nil {
		return fmt.Errorf("error starting %s: %v", command, err)
	}

	output, _ := io.ReadAll(stderr)
	err = cmd.Wait()
	if err != nil {
		return fmt.Errorf("%s %v returned with error code %v due to: %v", command, args, err, string(output))
	}
	return nil
}

func ensureConfigDirExists(configDir string) error {
	ok, err := config.FileExists(configDir)
	if !ok && err == nil {
		err = os.Mkdir(configDir, 0755)
	}
	return err
}

func copyFile(src, dest string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	ok := false
	defer func() {
		destFile.Close()
		if !ok {
			_ = os.Remove(dest)
		}
	}()

	if _, err := io.Copy(destFile, srcFile); err != nil {
		return err
	}

	ok = true
	return nil
}
