package server

import (
	"bufio"
	"encoding/json"
	"log/slog"
	"os"
	"sync"

	"github.com/tidbcloud/observability/vm-migration/internal/jsonio"
)

// jobsMu serializes appends to jobs.jsonl within this process (serve is
// single-instance per migrations dir). The writers are the run goroutine's
// job-end record and resume-time interrupted-record finalization; without
// the mutex those two can overlap on the migrate-run resume path.
var jobsMu sync.Mutex

// appendJobRecord appends one jobRecord as a single line to path, fsynced
// before close: the in-flight marker is only removed after a successful
// append, so an acknowledged record must survive power loss. Regular files
// carry no PIPE_BUF-style atomicity; the full buffer is written and checked.
// Appends are serialized by jobsMu — do not append to jobs.jsonl outside
// this function.
func appendJobRecord(path string, r *jobRecord) error {
	data, err := json.Marshal(r)
	if err != nil {
		return err
	}
	jobsMu.Lock()
	defer jobsMu.Unlock()
	return jsonio.AppendSynced(path, append(data, '\n'))
}

// readJobRecords parses every jobRecord line in path.
//
// readJobRecords returns an empty slice when the file does not exist.
// Malformed lines are logged and skipped so a single bad line doesn't mask
// every record after it.
func readJobRecords(path string) ([]jobRecord, error) {
	f, err := os.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return []jobRecord{}, nil
		}
		return nil, err
	}
	defer f.Close()
	out := []jobRecord{}
	scanner := bufio.NewScanner(f)
	scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	for scanner.Scan() {
		line := scanner.Bytes()
		if len(line) == 0 {
			continue
		}
		var r jobRecord
		if err := json.Unmarshal(line, &r); err != nil {
			slog.Warn("jobs.jsonl skip malformed line", "path", path, "err", err)
			continue
		}
		out = append(out, r)
	}
	if err := scanner.Err(); err != nil {
		return out, err
	}
	return out, nil
}
