package listener

import (
	"context"
	"fmt"
	"log"
	"time"

	"go.bug.st/serial"
	"tps.local/labbridge-agent/internal/astm"
	syncpkg "tps.local/labbridge-agent/internal/sync"
)

// serialListener holds the open port handle so the manager can Close() it.
type serialListener struct{ port serial.Port }

func (s *serialListener) Close() error { return s.port.Close() }

// startSerial opens an RS-232 port and runs the ASTM receiver in a goroutine.
// Reconnects automatically if the underlying port reports EOF (cable unplug,
// device power-cycle).
func startSerial(ctx context.Context, dev syncpkg.Device, sink Sink) (*serialListener, error) {
	if dev.ComPort == "" {
		return nil, fmt.Errorf("com_port not set")
	}
	mode := buildMode(dev)

	port, err := serial.Open(dev.ComPort, mode)
	if err != nil {
		return nil, fmt.Errorf("open %s: %w", dev.ComPort, err)
	}

	go func() {
		<-ctx.Done()
		_ = port.Close()
	}()

	go func() {
		for {
			if ctx.Err() != nil {
				return
			}
			sess, err := astm.Receive(port)
			if err != nil {
				log.Printf("listener: serial %s ASTM error: %v", dev.ComPort, err)
				time.Sleep(2 * time.Second)
				continue
			}
			if sess.Records != "" {
				deviceID := dev.ID
				sink(&deviceID, dev.Vendor, sess.Records, dev.ComPort, time.Now())
			}
		}
	}()

	return &serialListener{port: port}, nil
}

func buildMode(dev syncpkg.Device) *serial.Mode {
	m := &serial.Mode{
		BaudRate: dev.Baud,
		DataBits: dev.DataBits,
		Parity:   serial.NoParity,
		StopBits: serial.OneStopBit,
	}
	if m.BaudRate == 0 {
		m.BaudRate = 9600
	}
	if m.DataBits == 0 {
		m.DataBits = 8
	}
	switch dev.Parity {
	case "even", "E":
		m.Parity = serial.EvenParity
	case "odd", "O":
		m.Parity = serial.OddParity
	}
	if dev.StopBits == 2 {
		m.StopBits = serial.TwoStopBits
	}
	return m
}
