const { pool } = require('../../connection');
const { project } = require('../../utils');

async function get(options = {}) {
	/*
	 * ts0: earliest timestamp (default: NOW - 7 days)
	 * ts1: latest timestamp (if null, assume NOW)
	 * di: decimation interval (return every di-th record, if null: no decimation)
	 * l: limit (return no more than l records, default: 1000, max: 1,000,000)
	 * geojson: 'LineString' (GeoJSON LineString Feature), 'Point' (GeoJSON FeatureCollection),
	 *          truthy (array of Point features), falsy (array of {x, y, tstamp, meta})
	 */
	let { l, di, ts1, ts0, geojson, projection } = {
		l: 1000,
		di: null,
		ts1: null,
		ts0: null,
		geojson: false,
		...options
	};

	// Input validation and sanitization
	l = Math.max(1, Math.min(parseInt(l) || 1000, 1000000)); // Enforce 1 <= l <= 1,000,000
	di = di != null ? Math.max(1, parseInt(di)) : null; // Ensure di is positive integer or null
	ts0 = ts0 ? new Date(ts0).toISOString() : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); // Default: 7 days ago
	ts1 = ts1 ? new Date(ts1).toISOString() : null; // Convert to ISO string or null
	geojson = geojson === 'LineString' || geojson === 'Point' ? geojson : !!geojson; // Normalize geojson

	const client = await pool.connect();

	// Build the WHERE clause and values array dynamically
	let whereClauses = [];
	let values = [];
	let paramIndex = 1;

	if (ts0) {
		whereClauses.push(`tstamp >= $${paramIndex++}`);
		values.push(ts0);
	}
	if (ts1) {
		whereClauses.push(`tstamp <= $${paramIndex++}`);
		values.push(ts1);
	}

	// Add limit to values
	values.push(l);
	const limitClause = `LIMIT $${paramIndex++}`;

	// Base query with conditional geometry selection
	let queryText = `
		SELECT
			tstamp,
			CASE
				WHEN meta->>'lineStatus' = 'offline' THEN
					ARRAY[COALESCE((meta->>'longitude')::float, ST_X(geometry)), COALESCE((meta->>'latitude')::float, ST_Y(geometry))]
				ELSE
					ARRAY[
						COALESCE(
							(meta->>'longitudeMaster')::float,
							ST_X(geometry)
						),
						COALESCE(
							(meta->>'latitudeMaster')::float,
							ST_Y(geometry)
						)
					]
			END AS coordinates,
			meta::json AS meta
		FROM public.real_time_inputs
		${whereClauses.length ? 'WHERE ' + whereClauses.join(' AND ') : ''}
		ORDER BY tstamp DESC
		${limitClause}
	`;

	// If decimation is requested, wrap the query in a subquery with ROW_NUMBER
	if (di != null && di > 1) {
		values.push(di);
		queryText = `
			SELECT tstamp, coordinates, meta
			FROM (
				SELECT
					tstamp,
					CASE
						WHEN meta->>'lineStatus' = 'offline' THEN
							ARRAY[COALESCE((meta->>'longitude')::float, ST_X(geometry)), COALESCE((meta->>'latitude')::float, ST_Y(geometry))]
						ELSE
							ARRAY[
								COALESCE(
									(meta->>'longitudeMaster')::float,
									ST_X(geometry)
								),
								COALESCE(
									(meta->>'latitudeMaster')::float,
									ST_Y(geometry)
								)
							]
					END AS coordinates,
					meta::json AS meta,
					ROW_NUMBER() OVER (ORDER BY tstamp DESC) AS rn
				FROM public.real_time_inputs
				${whereClauses.length ? 'WHERE ' + whereClauses.join(' AND ') : ''}
			) sub
			WHERE rn % $${paramIndex} = 0
			ORDER BY tstamp DESC
			${limitClause}
		`;
	}

	try {
		const res = await client.query(queryText, values);
		if (!res.rows?.length) {
			throw { status: 204 }; // No Content
		}

		// Process rows: Convert tstamp to Date and extract coordinates
		let processed = res.rows.map(row => ({
			tstamp: new Date(row.tstamp),
			x: row.coordinates[0], // Longitude
			y: row.coordinates[1], // Latitude
			meta: projection != null
			? projection == ""
				? undefined
				: project([row.meta], projection)[0] : row.meta
		}));

		// Handle geojson output formats
		if (geojson === 'LineString') {
			// Compute line length (haversine formula in JavaScript for simplicity)
			let length = 0;
			for (let i = 1; i < processed.length; i++) {
				const p1 = processed[i - 1];
				const p2 = processed[i];
				const R = 6371e3; // Earth's radius in meters
				const φ1 = p1.y * Math.PI / 180;
				const φ2 = p2.y * Math.PI / 180;
				const Δφ = (p2.y - p1.y) * Math.PI / 180;
				const Δλ = (p2.x - p1.x) * Math.PI / 180;
				const a = Math.sin(Δφ / 2) ** 2 +
						 Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
				const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
				length += R * c;
			}

			return {
				type: 'Feature',
				geometry: {
					type: 'LineString',
					coordinates: processed.map(p => [p.x, p.y])
				},
				properties: {
					ts0: processed[processed.length - 1].tstamp.toISOString(),
					ts1: processed[0].tstamp.toISOString(),
					distance: length
				}
			};
		} else if (geojson === 'Point') {
			return {
				type: 'FeatureCollection',
				features: processed.map(p => ({
					type: 'Feature',
					geometry: {
						type: 'Point',
						coordinates: [p.x, p.y]
					},
					properties: {
						...p.meta,
						tstamp: p.tstamp.toISOString()
					}
				}))
			};
		} else if (geojson) {
			return processed.map(p => ({
				type: 'Feature',
				geometry: {
					type: 'Point',
					coordinates: [p.x, p.y]
				},
				properties: {
					...p.meta,
					tstamp: p.tstamp.toISOString()
				}
			}));
		} else {
			return processed;
		}
	} finally {
		client.release();
	}
}

module.exports = get;
