package app

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path"

	"git.ophivana.moe/cat/fortify/acl"
	"git.ophivana.moe/cat/fortify/internal/state"
)

const (
	pulseServer = "PULSE_SERVER"
	pulseCookie = "PULSE_COOKIE"

	home          = "HOME"
	xdgConfigHome = "XDG_CONFIG_HOME"
)

var (
	ErrPulseCookie = errors.New("pulse cookie not present")
	ErrPulseSocket = errors.New("pulse socket not present")
	ErrPulseMode   = errors.New("unexpected pulse socket mode")
)

type (
	PulseCookieAccessError BaseError
	PulseSocketAccessError BaseError
)

func (seal *appSeal) sharePulse() error {
	if !seal.et.Has(state.EnablePulse) {
		return nil
	}

	// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
	pd := path.Join(seal.RuntimePath, "pulse")
	ps := path.Join(pd, "native")
	if _, err := os.Stat(pd); err != nil {
		if !errors.Is(err, fs.ErrNotExist) {
			return (*PulseSocketAccessError)(wrapError(err,
				fmt.Sprintf("cannot access PulseAudio directory '%s':", pd), err))
		}
		return (*PulseSocketAccessError)(wrapError(ErrPulseSocket,
			fmt.Sprintf("PulseAudio directory '%s' not found", pd)))
	}

	seal.appendEnv(pulseServer, "unix:"+ps)
	seal.sys.updatePerm(pd, acl.Execute)

	// ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
	if s, err := os.Stat(ps); err != nil {
		if !errors.Is(err, fs.ErrNotExist) {
			return (*PulseSocketAccessError)(wrapError(err,
				fmt.Sprintf("cannot access PulseAudio socket '%s':", ps), err))
		}
		return (*PulseSocketAccessError)(wrapError(ErrPulseSocket,
			fmt.Sprintf("PulseAudio directory '%s' found but socket does not exist", pd)))
	} else {
		if m := s.Mode(); m&0o006 != 0o006 {
			return (*PulseSocketAccessError)(wrapError(ErrPulseMode,
				fmt.Sprintf("unexpected permissions on '%s':", ps), m))
		}
	}

	// publish current user's pulse cookie for target user
	if src, err := discoverPulseCookie(); err != nil {
		return err
	} else {
		dst := path.Join(seal.share, "pulse-cookie")
		seal.appendEnv(pulseCookie, dst)
		seal.sys.copyFile(dst, src)
	}

	return nil
}

// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
func discoverPulseCookie() (string, error) {
	if p, ok := os.LookupEnv(pulseCookie); ok {
		return p, nil
	}

	// dotfile $HOME/.pulse-cookie
	if p, ok := os.LookupEnv(home); ok {
		p = path.Join(p, ".pulse-cookie")
		if s, err := os.Stat(p); err != nil {
			if !errors.Is(err, fs.ErrNotExist) {
				return p, (*PulseCookieAccessError)(wrapError(err,
					fmt.Sprintf("cannot access PulseAudio cookie '%s':", p), err))
			}
			// not found, try next method
		} else if !s.IsDir() {
			return p, nil
		}
	}

	// $XDG_CONFIG_HOME/pulse/cookie
	if p, ok := os.LookupEnv(xdgConfigHome); ok {
		p = path.Join(p, "pulse", "cookie")
		if s, err := os.Stat(p); err != nil {
			if !errors.Is(err, fs.ErrNotExist) {
				return p, (*PulseCookieAccessError)(wrapError(err, "cannot access PulseAudio cookie", p+":", err))
			}
			// not found, try next method
		} else if !s.IsDir() {
			return p, nil
		}
	}

	return "", (*PulseCookieAccessError)(wrapError(ErrPulseCookie,
		fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
			pulseCookie, xdgConfigHome, home)))
}