package main

import (
	"context"
	"time"

	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/influxdata/influxdb-client-go/v2/api"
)

const responseTimeout = 2 * time.Second

// Poller periodically publishes FC4 read requests for every configured register
// on every device, waiting for each response before sending the next request.
// After all registers for a device are collected, one InfluxDB point is written.
type Poller struct {
	requester *Requester
	handler   *handler
	writeAPI  api.WriteAPIBlocking
	org       string
	bucket    string
	devices   []DeviceConfig
	interval  time.Duration
}

func NewPoller(r *Requester, h *handler, writeAPI api.WriteAPIBlocking, cfg Config, devices []DeviceConfig, interval time.Duration) *Poller {
	return &Poller{
		requester: r,
		handler:   h,
		writeAPI:  writeAPI,
		org:       cfg.InfluxOrg,
		bucket:    cfg.InfluxBucket,
		devices:   devices,
		interval:  interval,
	}
}

// Run blocks until ctx is cancelled, polling immediately on start.
func (p *Poller) Run(ctx context.Context) {
	p.poll(ctx)
	ticker := time.NewTicker(p.interval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			p.poll(ctx)
		}
	}
}

func (p *Poller) poll(ctx context.Context) {
	now := time.Now()
	for _, dev := range p.devices {
		point := influxdb2.NewPointWithMeasurement(dev.Name).SetTime(now)
		hasFields := false

		for _, reg := range dev.Registers {
			if reg.Type != "input" {
				logger.Printf("skipping holding register %s/%s (FC3 not implemented)", dev.Name, reg.Name)
				continue
			}

			// Register the waiter before publishing to avoid a race with a fast response.
			respCh := p.handler.waitForResponse(dev.SlaveAddress)

			if err := p.requester.ReadInputRegisters(dev.Segment, byte(dev.SlaveAddress), reg.Address, reg.Count); err != nil {
				logger.Printf("poll %s/%s: %v", dev.Name, reg.Name, err)
				continue
			}

			select {
			case readings := <-respCh:
				for _, r := range readings {
					point.AddField(r.Name, r.Value)
					hasFields = true
				}
			case <-time.After(responseTimeout):
				logger.Printf("timeout waiting for response: %s/%s", dev.Name, reg.Name)
			case <-ctx.Done():
				return
			}
		}

		if !hasFields {
			continue
		}
		if err := p.writeAPI.WritePoint(ctx, point); err != nil {
			logger.Printf("influx write %s: %v", dev.Name, err)
		}
	}
}
