package db

import (
	"context"
	"fmt"
	"time"
)

// InstanceForwardRow is one panel-owned port forward in opnfwd. Composite
// key (instance_id, port_name) — the panel allocates one forward per
// manifest port per instance.
type InstanceForwardRow struct {
	InstanceID string
	PortName   string
	NatUUID    string
	FilterUUID string
	TargetIP   string
	HostPort   int32
	Proto      string // "tcp" | "udp" | "tcp/udp"
	Descr      string // "panel-<instance>-<port_name>" — matches the OPNsense rule's <descr>
	CreatedAt  time.Time
}

// UpsertInstanceForward persists a single forward, replacing any prior row
// for the same (instance, port_name). The replace is intentional — if the
// panel re-creates a forward for an existing port (e.g. operator clicked
// Reconcile and we recreated), the new uuids supersede the old.
func (db *DB) UpsertInstanceForward(ctx context.Context, r InstanceForwardRow) error {
	_, err := db.pool.Exec(ctx, `
		INSERT INTO instance_forwards (instance_id, port_name, nat_uuid, filter_uuid, target_ip, host_port, proto, descr)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		ON CONFLICT (instance_id, port_name) DO UPDATE SET
			nat_uuid    = EXCLUDED.nat_uuid,
			filter_uuid = EXCLUDED.filter_uuid,
			target_ip   = EXCLUDED.target_ip,
			host_port   = EXCLUDED.host_port,
			proto       = EXCLUDED.proto,
			descr       = EXCLUDED.descr
	`, r.InstanceID, r.PortName, r.NatUUID, r.FilterUUID, r.TargetIP, r.HostPort, r.Proto, r.Descr)
	if err != nil {
		return fmt.Errorf("upsert instance_forward: %w", err)
	}
	return nil
}

// ListInstanceForwards returns every forward for the given instance, used
// at delete time + by the instance-modal "Forwards" pill.
func (db *DB) ListInstanceForwards(ctx context.Context, instanceID string) ([]InstanceForwardRow, error) {
	rows, err := db.pool.Query(ctx, `
		SELECT instance_id, port_name, nat_uuid, filter_uuid, target_ip, host_port, proto, descr, created_at
		FROM instance_forwards
		WHERE instance_id = $1
		ORDER BY port_name
	`, instanceID)
	if err != nil {
		return nil, fmt.Errorf("list instance_forwards: %w", err)
	}
	defer rows.Close()
	var out []InstanceForwardRow
	for rows.Next() {
		var r InstanceForwardRow
		if err := rows.Scan(&r.InstanceID, &r.PortName, &r.NatUUID, &r.FilterUUID,
			&r.TargetIP, &r.HostPort, &r.Proto, &r.Descr, &r.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan instance_forward: %w", err)
		}
		out = append(out, r)
	}
	return out, rows.Err()
}

// ListAllInstanceForwards is for the reconcile path — compare every
// panel-tracked forward against opnfwd's view of the world.
func (db *DB) ListAllInstanceForwards(ctx context.Context) ([]InstanceForwardRow, error) {
	rows, err := db.pool.Query(ctx, `
		SELECT instance_id, port_name, nat_uuid, filter_uuid, target_ip, host_port, proto, descr, created_at
		FROM instance_forwards
		ORDER BY instance_id, port_name
	`)
	if err != nil {
		return nil, fmt.Errorf("list all instance_forwards: %w", err)
	}
	defer rows.Close()
	var out []InstanceForwardRow
	for rows.Next() {
		var r InstanceForwardRow
		if err := rows.Scan(&r.InstanceID, &r.PortName, &r.NatUUID, &r.FilterUUID,
			&r.TargetIP, &r.HostPort, &r.Proto, &r.Descr, &r.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan instance_forward: %w", err)
		}
		out = append(out, r)
	}
	return out, rows.Err()
}

// DeleteInstanceForwards removes every panel-tracked forward for an
// instance. Called from the delete path AFTER opnfwd's bulk-delete
// returned successfully — this just drops the local mirror.
func (db *DB) DeleteInstanceForwards(ctx context.Context, instanceID string) error {
	_, err := db.pool.Exec(ctx, `DELETE FROM instance_forwards WHERE instance_id = $1`, instanceID)
	if err != nil {
		return fmt.Errorf("delete instance_forwards: %w", err)
	}
	return nil
}
