// Package bus provides NATS messaging for the poller service.
//
// tunnel_responder.go wires the tunnel.Manager to NATS subjects tunnel.open,
// tunnel.close, tunnel.status, and tunnel.status.list.
package bus

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"time"

	"github.com/nats-io/nats.go"

	"github.com/staack/the-other-dude/poller/internal/store"
	"github.com/staack/the-other-dude/poller/internal/tunnel"
	"github.com/staack/the-other-dude/poller/internal/vault"
)

// TunnelOpenRequest is the JSON payload for a tunnel.open NATS request.
type TunnelOpenRequest struct {
	DeviceID   string `json:"device_id"`
	TenantID   string `json:"tenant_id"`
	UserID     string `json:"user_id"`
	TargetPort int    `json:"target_port"`
}

// TunnelCloseRequest is the JSON payload for a tunnel.close NATS request.
type TunnelCloseRequest struct {
	TunnelID string `json:"tunnel_id"`
}

// TunnelStatusRequest is the JSON payload for tunnel.status and
// tunnel.status.list NATS requests.
type TunnelStatusRequest struct {
	TunnelID string `json:"tunnel_id,omitempty"`
	DeviceID string `json:"device_id,omitempty"`
}

// TunnelResponder handles NATS request-reply for WinBox tunnel management.
type TunnelResponder struct {
	nc          *nats.Conn
	manager     *tunnel.Manager
	deviceStore *store.DeviceStore
	credCache   *vault.CredentialCache
	subs        []*nats.Subscription
}

// NewTunnelResponder creates a TunnelResponder using the given NATS connection,
// tunnel manager, device store, and credential cache.
func NewTunnelResponder(nc *nats.Conn, mgr *tunnel.Manager, ds *store.DeviceStore, cc *vault.CredentialCache) *TunnelResponder {
	return &TunnelResponder{nc: nc, manager: mgr, deviceStore: ds, credCache: cc}
}

// Subscribe registers NATS handlers for tunnel.open, tunnel.close,
// tunnel.status, and tunnel.status.list.
func (tr *TunnelResponder) Subscribe() error {
	subjects := []struct {
		subject string
		handler nats.MsgHandler
	}{
		{"tunnel.open", tr.handleOpen},
		{"tunnel.close", tr.handleClose},
		{"tunnel.status", tr.handleStatus},
		{"tunnel.status.list", tr.handleStatusList},
	}

	for _, s := range subjects {
		sub, err := tr.nc.Subscribe(s.subject, s.handler)
		if err != nil {
			return fmt.Errorf("subscribing to %s: %w", s.subject, err)
		}
		tr.subs = append(tr.subs, sub)
	}

	slog.Info("tunnel NATS responder subscribed")
	return nil
}

// Stop unsubscribes all tunnel NATS subscriptions.
func (tr *TunnelResponder) Stop() {
	for _, sub := range tr.subs {
		if err := sub.Unsubscribe(); err != nil {
			slog.Warn("error unsubscribing tunnel responder", "error", err)
		}
	}
}

// handleOpen processes a tunnel.open request: looks up the device, derives
// the remote address, and delegates to the tunnel Manager.
func (tr *TunnelResponder) handleOpen(msg *nats.Msg) {
	var req TunnelOpenRequest
	if err := json.Unmarshal(msg.Data, &req); err != nil {
		tr.respondError(msg, "invalid request")
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	dev, err := tr.deviceStore.GetDevice(ctx, req.DeviceID)
	if err != nil {
		slog.Error("tunnel: device lookup failed", "device_id", req.DeviceID, "err", err)
		tr.respondError(msg, "device not found")
		return
	}

	targetPort := req.TargetPort
	if targetPort == 0 {
		targetPort = 8291
	}
	remoteAddr := fmt.Sprintf("%s:%d", dev.IPAddress, targetPort)

	resp, err := tr.manager.OpenTunnel(req.DeviceID, req.TenantID, req.UserID, remoteAddr)
	if err != nil {
		slog.Error("tunnel: open failed", "device_id", req.DeviceID, "err", err)
		tr.respondError(msg, err.Error())
		return
	}

	data, _ := json.Marshal(resp)
	if err := msg.Respond(data); err != nil {
		slog.Error("tunnel: failed to respond to open request", "error", err)
	}
}

// handleClose processes a tunnel.close request.
func (tr *TunnelResponder) handleClose(msg *nats.Msg) {
	var req TunnelCloseRequest
	if err := json.Unmarshal(msg.Data, &req); err != nil {
		tr.respondError(msg, "invalid request")
		return
	}

	if err := tr.manager.CloseTunnel(req.TunnelID); err != nil {
		tr.respondError(msg, err.Error())
		return
	}

	if err := msg.Respond([]byte(`{"ok":true}`)); err != nil {
		slog.Error("tunnel: failed to respond to close request", "error", err)
	}
}

// handleStatus processes a tunnel.status request for a single tunnel.
func (tr *TunnelResponder) handleStatus(msg *nats.Msg) {
	var req TunnelStatusRequest
	if err := json.Unmarshal(msg.Data, &req); err != nil {
		tr.respondError(msg, "invalid request")
		return
	}

	status, err := tr.manager.GetTunnel(req.TunnelID)
	if err != nil {
		tr.respondError(msg, err.Error())
		return
	}

	data, _ := json.Marshal(status)
	if err := msg.Respond(data); err != nil {
		slog.Error("tunnel: failed to respond to status request", "error", err)
	}
}

// handleStatusList processes a tunnel.status.list request, returning all
// tunnels for the given device_id.
func (tr *TunnelResponder) handleStatusList(msg *nats.Msg) {
	var req TunnelStatusRequest
	if err := json.Unmarshal(msg.Data, &req); err != nil {
		tr.respondError(msg, "invalid request")
		return
	}

	list := tr.manager.ListTunnels(req.DeviceID)
	data, _ := json.Marshal(list)
	if err := msg.Respond(data); err != nil {
		slog.Error("tunnel: failed to respond to status list request", "error", err)
	}
}

// respondError sends a JSON error response to a NATS request.
func (tr *TunnelResponder) respondError(msg *nats.Msg, errMsg string) {
	resp, _ := json.Marshal(map[string]string{"error": errMsg})
	if err := msg.Respond(resp); err != nil {
		slog.Error("tunnel: failed to respond with error", "error", err)
	}
}
