package hamview

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
)

type JSONB map[string]any

func (v JSONB) Value() (driver.Value, error) {
	return json.Marshal(v)
}

func (v *JSONB) Scan(value any) error {
	if value == nil {
		*v = nil
		return nil
	}
	b, ok := value.([]byte)
	if !ok {
		return fmt.Errorf("type assertion to []byte failed, got %T", value)
	}

	return json.Unmarshal(b, &v)
}

/*
const (
	sqlCreateRadio = `
	CREATE TABLE IF NOT EXISTS radio (
		id			 		BIGSERIAL PRIMARY KEY,
		name				VARCHAR(64) NOT NULL UNIQUE,
		is_online           BOOLEAN NOT NULL DEFAULT false,
		device          	VARCHAR(100),
		manufacturer    	VARCHAR(100),
		firmware_date  		TIMESTAMPTZ,
		firmware_version 	VARCHAR(32),
		antenna             VARCHAR(100),
		modulation          VARCHAR(16) NOT NULL,
		protocol			VARCHAR(16) NOT NULL,
		latitude 			NUMERIC(10, 8), 					-- GPS latitude in decimal degrees
    	longitude 			NUMERIC(11, 8),  					-- GPS longitude in decimal degrees
		altitude            REAL,								-- Altitude in meters
		frequency			DOUBLE PRECISION NOT NULL,
		bandwidth			DOUBLE PRECISION NOT NULL,
		rx_frequency		DOUBLE PRECISION,
		tx_frequency 		DOUBLE PRECISION,
		power				REAL,
		gain				REAL,
		lora_sf				SMALLINT,
		lora_cr				SMALLINT,
		extra				JSONB,
		created_at			TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
		updated_at 			TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
	);
	`
	sqlIndexRadioName        = `CREATE INDEX IF NOT EXISTS idx_radio_name ON radio(name);`
	sqlIndexRadioProtocol    = `CREATE INDEX IF NOT EXISTS idx_radio_protocol ON radio(protocol);`
	sqlGeometryRadioPosition = `SELECT AddGeometryColumn('public', 'radio', 'position', 4326, 'POINT', 2);`
	sqlSelectRadios          = `
	SELECT
		name,
		is_online,
		device,
		manufacturer,
		firmware_date,
		firmware_version,
		antenna,
		modulation,
		protocol,
		latitude,
    	longitude,
		altitude,
		frequency,
		bandwidth,
		rx_frequency,
		tx_frequency,
		power,
		gain,
		lora_sf,
		lora_cr,
		extra
	FROM radio
	WHERE
		protocol LIKE $1
	AND (
			is_online
		OR
			updated_at BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()
	)
	ORDER BY
		protocol, name
	`
)

const (
	sqlCreateAPRSStation = `
	CREATE TABLE IF NOT EXISTS aprs_station (
		id			 	BIGSERIAL PRIMARY KEY,
		address         VARCHAR(10) NOT NULL UNIQUE,
		last_heard      TIMESTAMPTZ NOT NULL,
		last_path       TEXT[],
		last_comment    TEXT
	);
	`
	sqlCreateAPRSPacket = `
	CREATE TABLE IF NOT EXISTS aprs_packet (
		id			 	BIGSERIAL PRIMARY KEY,
		src_address     VARCHAR(10) NOT NULL,
		dst_address     VARCHAR(10) NOT NULL,
		comment         TEXT,
		payload 		BYTEA,
		raw             BYTEA,
		received_at	 	TIMESTAMPTZ NOT NULL DEFAULT NOW()
	);
	`
)

const (
	sqlCreateMeshCorePacket = `
	CREATE TABLE IF NOT EXISTS meshcore_packet (
		id			 	BIGSERIAL PRIMARY KEY,
		snr				REAL NOT NULL DEFAULT 0,
		rssi			SMALLINT NOT NULL DEFAULT 0,
		hash          	BYTEA NOT NULL,				-- Used for deduplication
		route_type      SMALLINT NOT NULL,
		payload_type    SMALLINT NOT NULL,
		path            BYTEA,
		payload 		BYTEA,
		raw             BYTEA,
		parsed			JSONB,
		received_at	 	TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		created_at   	TIMESTAMPTZ NOT NULL DEFAULT NOW()
	);
	`
	sqlIndexMeshCorePacketHash        = `CREATE INDEX IF NOT EXISTS idx_meshcore_packet_hash ON meshcore_packet(hash);`
	sqlIndexMeshCorePacketPayloadType = `CREATE INDEX IF NOT EXISTS idx_meshcore_packet_payload_type ON meshcore_packet(payload_type);`
	sqlSelectMeshCoreTextStats        = `
	SELECT
		COUNT(id)
	FROM
		meshcore_packet
	WHERE
		received_at BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()
	AND
		payload_type IN (2, 5)
	`
	sqlSelectMeshCorePacketStatsOverTime = `
	WITH time_buckets AS (
		SELECT
			time_bucket,
			COUNT(*) as packet_count
		FROM (
			SELECT
				date_trunc('minute', received_at) - INTERVAL '1 minute' * (EXTRACT(minute FROM received_at)::int % 5) AS time_bucket
			FROM
				meshcore_packet
			WHERE
				received_at >= NOW() - INTERVAL '24 hours'
		) AS bucketed
		GROUP BY time_bucket
		ORDER BY time_bucket DESC
	)
	SELECT
  		time_bucket,
  		packet_count
	FROM
		time_buckets;
	`
)

const (
	sqlCreateMeshCoreNode = `
	CREATE TABLE IF NOT EXISTS meshcore_node (
		id			 	BIGSERIAL PRIMARY KEY,
		last_advert_id  BIGINT NOT NULL REFERENCES meshcore_packet(id) ON DELETE CASCADE,
		node_type       SMALLINT NOT NULL DEFAULT 0,
		public_key 		BYTEA NOT NULL UNIQUE,
		name 			TEXT,
		local_time      TIMESTAMPTZ NOT NULL,
		first_heard     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		last_heard  	TIMESTAMPTZ NOT NULL DEFAULT NOW(),
		last_latitude 	NUMERIC(10, 8), 					-- GPS latitude in decimal degrees
    	last_longitude 	NUMERIC(11, 8),  					-- GPS longitude in decimal degrees
		last_position   GEOMETRY(POINT, 4326)
	);
	`
	sqlIndexMeshCoreNodePublicKey    = `CREATE INDEX IF NOT EXISTS idx_meshcore_node_public_key ON meshcore_node(public_key);`
	sqlIndexMeshCoreNodeName         = `CREATE INDEX IF NOT EXISTS idx_meshcore_node_name ON meshcore_node(name);`
	sqlAlterMeshCoreNodePrefix       = `ALTER TABLE meshcore_node ADD COLUMN IF NOT EXISTS prefix BYTEA GENERATED ALWAYS AS (substring(public_key, 0, 2)) STORED;`
	sqlGeometryMeshCoreNodePosition  = `SELECT AddGeometryColumn('public', 'meshcore_node', 'position', 4326, 'POINT', 2);`
	sqlAlterMeshCoreNodeLastPosition = `
	ALTER TABLE meshcore_node
	ADD COLUMN last_position GEOMETRY(Point, 4326)
	GENERATED ALWAYS AS (
		CASE
			WHEN last_latitude IS NOT NULL AND last_longitude IS NOT NULL THEN ST_SetSRID(ST_MakePoint(last_latitude, last_longitude), 4326)
			ELSE NULL
		END
	) STORED;`
	sqlSelectMeshCoreNodeStats = `
	SELECT
		COUNT(id)
	FROM
		meshcore_node
	`
)

const (
	sqlCreateMeshCoreNodePosition = `
	CREATE TABLE IF NOT EXISTS meshcore_node_position (
		id			 	BIGSERIAL PRIMARY KEY,
		node_id 		BIGINT NOT NULL REFERENCES meshcore_node(id) ON DELETE CASCADE,
		heard_at		TIMESTAMPTZ NOT NULL,
		latitude        NUMERIC(10, 8), 					-- GPS latitude in decimal degrees
		longitude 		NUMERIC(11, 8)	 					-- GPS longitude in decimal degrees
	);
	`
	sqlGeometryMeshCoreNodePositionPosition = `SELECT AddGeometryColumn('public', 'meshcore_node_position', 'position', 4326, 'POINT', 2);`
	sqlIndexMeshCoreNodePositionPosition    = `CREATE INDEX IF NOT EXISTS idx_meshcore_node_position_position ON meshcore_node_position USING GIST (position);`
)

const (
	sqlSelectMeshCoreNodesLastPosition = `
		WITH ranked_positions AS (
		SELECT
			node_id, latitude, longitude, position,
			ROW_NUMBER() OVER (PARTITION BY node_id ORDER BY heard_at DESC) as rn
			FROM meshcore_node_position
		)
		SELECT
			r.snr,
			r.rssi,
			n.name,
			n.public_key,
			n.prefix,
			n.node_type,
			n.first_heard,
			n.last_heard,
			p.latitude,
			p.longitude
		FROM
			meshcore_node n
		LEFT JOIN ranked_positions p ON n.id = p.node_id AND p.rn = 1
		LEFT JOIN meshcore_packet r ON r.id = n.last_advert_id
		WHERE
			n.node_type = $1
		ORDER BY last_heard DESC LIMIT $2;
	`
	sqlSelectMeshCorePackets = `
		SELECT
			snr,
			rssi,
			hash,
			route_type,
			payload_type,
			path,
			received_at,
			raw,
			parsed
		FROM
			meshcore_packet
		ORDER BY
			received_at DESC
		LIMIT $1;
	`
	sqlSelectMeshCorePacketsByHash = `
		SELECT
			snr,
			rssi,
			hash,
			route_type,
			payload_type,
			path,
			received_at,
			raw,
			parsed
		FROM
			meshcore_packet
		WHERE
			hash = $1
		ORDER BY
			received_at DESC;
	`
	sqlSelectMeshCorePacketsByRepeaterWindowed = `
		SELECT
			to_timestamp(round(EXTRACT(EPOCH FROM received_at) / $1) * $1) as window,
			cast(to_hex(get_byte(path, length(path)-2)) as text) AS repeater,
			count(id) AS packets
		FROM
			meshcore_packet
		WHERE
			length(path) >= 2 AND
			received_at >= $2
		GROUP BY
			round(EXTRACT(EPOCH FROM received_at) / $1),
			cast(to_hex(get_byte(path, length(path)-2)) as text);
	`
	sqlSelectMeshCorePacketPathNodes = `
	WITH RECURSIVE
	params AS (
		$1::BYTEA as path,
		$2::NUMERIC(10, 8) as start_latitude,
		$3::NUMERIC(11, 8) as start_longitude,
		$4::DOUBLE PRECISION as max_range_m
	),

	path_prefix AS (
		SELECT
	)
	`
)

const (
	sqlCreateMeshCoreIdentity = `
	CREATE TABLE IF NOT EXISTS meshcore_identity (
		id			 	BIGSERIAL PRIMARY KEY,
		name            VARCHAR(32) NOT NULL UNIQUE,
		public_key      BYTEA(32) NOT NULL UNIQUE,
		private_key     BYTEA(64) NOT NULL
	);
	`
)

const (
	sqlCreateMeshCoreGroup = `
	CREATE TABLE IF NOT EXISTS meshcore_group (
		id			 	BIGSERIAL PRIMARY KEY,
		name            VARCHAR(32) NOT NULL UNIQUE,
		hash            SMALLINT NOT NULL,
		shared_key      VARCHAR(64) NOT NULL,
		is_public       BOOLEAN NOT NULL DEFAULT FALSE
	);
	`
)
*/
