// This file is part of the adequate Debian-native package, and is available
// under the Expat license. For the full terms please see debian/copyright.

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"syscall"
)

const pendingPath = "/var/lib/adequate/pending"

func aptHookReader() *bufio.Reader {
	return bufio.NewReader(os.Stdin)
}

func aptHookIsEnabled(reader *bufio.Reader) bool {
	var enabled, defined bool

loop:
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			// We're only supposed to read the configuration
			// section, which ends with an empty line, so we
			// shouldn't hit EOF.
			if err == io.EOF {
				log.Fatal("Found EOF while reading apt configuration section.")
			}
			log.Fatal("Read error: ", err)
		}

		switch line {
		case "Adequate::Enabled=true\n":
			enabled = true
			defined = true
		case "Adequate::Enabled=false\n":
			enabled = false
			defined = true
		case "\n":
			break loop
		}
	}

	if !defined {
		log.Println("apt hook is not enabled")
		return false
	}
	return enabled
}

func readPkgs(reader *bufio.Reader) []string {
	var pkgs []string

	for {
		line, err := reader.ReadString('\n')
		if err != nil && err != io.EOF {
			log.Fatal("Read error: ", err)
		}

		if line == "" || err == io.EOF {
			break
		}
		// We're looking for files with this structure:
		//	pkg, fromVer, operator, toVer, debFilePath
		// Sample line:
		// libparted-fs-resize0 3.5-3 = 3.5-3 /var/cache/apt/archives/libparted-fs-resize0_3.5-3_amd64.deb
		fields := strings.Fields(line)
		if n := len(fields); n != 5 || !strings.HasSuffix(line, ".deb\n") {
			continue
		}
		pkgs = append(pkgs, fields[0])
	}
	return pkgs
}

type pendingHandler struct {
	fi *os.File
}

func newPendingHandler() (*pendingHandler, error) {
	fi, err := os.OpenFile(pendingPath, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", pendingPath, err)
	}
	if err := syscall.Flock(int(fi.Fd()), syscall.LOCK_EX); err != nil {
		return nil, fmt.Errorf("%s: %w", pendingPath, err)
	}
	return &pendingHandler{
		fi: fi,
	}, nil
}

func (ph *pendingHandler) read() ([]string, error) {
	if _, err := ph.fi.Seek(0, 0); err != nil {
		fmt.Errorf("failed to seek %s: %w", pendingPath, err)
	}
	return readLines(ph.fi)
}

func (ph *pendingHandler) truncatePendingOrDie() {
	ph.writePendingOrDie(nil)
}

func (ph *pendingHandler) writePendingOrDie(pkgs []string) {
	if ph.fi == nil {
		log.Fatal("writePendingOrDie() called with nil file handler")
	}
	if err := ph.fi.Truncate(0); err != nil {
		log.Fatalf("Failed to truncate %s: %v", pendingPath, err)
	}
	if _, err := ph.fi.Seek(0, 0); err != nil {
		log.Fatalf("Failed to seek %s: %v", pendingPath, err)
	}

	for _, pkg := range pkgs {
		if _, err := ph.fi.WriteString(pkg + "\n"); err != nil {
			log.Fatalf("Failed to write %s: %v", pendingPath, err)
		}
	}

}

func (ph *pendingHandler) cleanupOrDie() error {
	if err := syscall.Flock(int(ph.fi.Fd()), syscall.LOCK_UN); err != nil {
		log.Fatalf("Failed to release file lock for %s: %v", pendingPath, err)
	}
	return ph.fi.Close()
}
