package platform

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

	"git.casaderoll.de/michael/urbm/internal/model"
)

type CredentialGetter interface{ Get(string) (string, error) }

type MountManager struct {
	RuntimeDir string
	Secrets    CredentialGetter
}

type MountedRepository struct {
	Repository     model.Repository
	Mounted        bool
	CredentialFile string
}

func (m *MountManager) Prepare(ctx context.Context, repo model.Repository) (MountedRepository, error) {
	result := MountedRepository{Repository: repo}
	if repo.Type != model.RepositorySMB && repo.Type != model.RepositoryNFS {
		return result, nil
	}
	if repo.Mount == nil || !repo.Mount.Managed {
		if _, err := os.Stat(repo.Location); err != nil {
			return result, fmt.Errorf("connectivity: external mount unavailable: %w", err)
		}
		return result, nil
	}
	point := filepath.Clean(repo.Mount.MountPoint)
	if !strings.HasPrefix(point, "/mnt/remotes/") && !strings.HasPrefix(point, "/mnt/disks/") {
		return result, errors.New("validation: managed mountPoint must be below /mnt/remotes or /mnt/disks")
	}
	if err := os.MkdirAll(point, 0700); err != nil {
		return result, err
	}
	mountType := string(repo.Type)
	if repo.Type == model.RepositorySMB {
		mountType = "cifs"
	}
	args := []string{"-t", mountType, repo.Mount.Remote, point}
	if repo.Type == model.RepositorySMB {
		options := []string{"nosuid", "nodev"}
		if repo.CredentialRef != "" {
			credential, err := m.Secrets.Get(repo.CredentialRef)
			if err != nil {
				return result, fmt.Errorf("authentication: load mount credential: %w", err)
			}
			dir := filepath.Join(m.RuntimeDir, "secrets")
			if err := os.MkdirAll(dir, 0700); err != nil {
				return result, err
			}
			f, err := os.CreateTemp(dir, "smb-credentials-*")
			if err != nil {
				return result, err
			}
			result.CredentialFile = f.Name()
			if err := f.Chmod(0600); err != nil {
				f.Close()
				return result, err
			}
			if _, err := f.WriteString(credential); err != nil {
				f.Close()
				return result, err
			}
			if err := f.Close(); err != nil {
				return result, err
			}
			options = append(options, "credentials="+result.CredentialFile)
		}
		args = append(args, "-o", strings.Join(options, ","))
	} else {
		args = append(args, "-o", "nosuid,nodev")
	}
	if err := exec.CommandContext(ctx, "mount", args...).Run(); err != nil {
		m.Cleanup(context.Background(), result)
		return result, fmt.Errorf("connectivity: mount repository: %w", err)
	}
	result.Mounted = true
	result.Repository.Location = point
	return result, nil
}

func (m *MountManager) Cleanup(ctx context.Context, mounted MountedRepository) error {
	if mounted.CredentialFile != "" {
		defer os.Remove(mounted.CredentialFile)
	}
	if !mounted.Mounted {
		return nil
	}
	return exec.CommandContext(ctx, "umount", mounted.Repository.Location).Run()
}
