const { InfluxDB, Point } = require('@influxdata/influxdb-client');

const url = process.env.INFLX_URL;
const token = process.env.INFLX_TOKEN;
const org = process.env.INFLX_ORG;

const boatTelemetry = "boat"

const client = new InfluxDB({ url, token })
const write = client.getWriteApi(org, boatTelemetry);
const querying = client.getQueryApi(org);

console.log("InfluxDB client initialized with config:", { url, org, token });

async function append(measurement, sensor, data) {
    const point = new Point(measurement)
    .tag("sensor", sensor)
    .floatField('temperature', data.temperature)
    .floatField('humidity', data.humidity)

    write.writePoint(point);
    await write.flush();

}

async function writeBatch(datas) {
    datas.forEach(data => {
        append(data.measurement, data.sensor, data.data);
    })
}

async function query(bucket, relativeTime, measurement, sensor, field) {

    const fluxTimeRange = relativeTime || "-1h";
    let fluxQuery = `
    from(bucket: "${bucket}")
    |> range(start: ${fluxTimeRange})
    |> filter(fn: (r) => r._measurement == "${measurement}")`;

    if (sensor) {
        fluxQuery += `\n    |> filter(fn: (r) => r.sensor == "${sensor}")`;
    }

    if (field) {
        fluxQuery += `\n    |> filter(fn: (r) => r._field == "${field}")`;
    }

    fluxQuery += `\n    |> yield(name: "data")`;

    try {
        const data = [];
        for await (const { values, tableMeta } of querying.iterateRows(fluxQuery)) {
            data.push(tableMeta.toObject(values));
        }
        return data;
    } catch (error) {
        console.error("Error in query:", error);
        return [];
    }

}

const sessionBucket = 'boat';

/**
 * Query storica per una sessione di registrazione.
 * @param {string} sensor - nome sensore
 * @param {string} session - session_id (tag InfluxDB)
 * @param {string} since - ISO timestamp o duration (es. "-30d")
 * @param {string|null} until - ISO timestamp fine (opzionale)
 * @returns {Promise<Array<Object>>}
 */
async function querySessionHistory(sensor, session, since, until = null) {
    const rangeStr = until ? `start: ${since}, stop: ${until}` : `start: ${since}`;
    const fluxQuery = `
        from(bucket: "${sessionBucket}")
            |> range(${rangeStr})
            |> filter(fn: (r) => r._measurement == "logs")
            |> filter(fn: (r) => r.sensor == "${sensor}")
            |> filter(fn: (r) => r.session == "${session}")
            |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
            |> sort(columns: ["_time"])
    `;
    const rows = [];
    return new Promise((resolve, reject) => {
        querying.queryRows(fluxQuery, {
            next(row, tableMeta) { rows.push(tableMeta.toObject(row)); },
            error: reject,
            complete() { resolve(rows); },
        });
    });
}

/**
 * Esporta i dati di una sessione come stringa CSV.
 * @param {string} sensor
 * @param {string} session
 * @param {string} since
 * @param {string|null} until
 * @returns {Promise<string>}
 */
async function exportSessionCSV(sensor, session, since, until = null) {
    const rows = await querySessionHistory(sensor, session, since, until);
    if (rows.length === 0) return '';
    const metaKeys = new Set(['result', 'table', '_start', '_stop', '_measurement', 'sensor', 'session', '']);
    const fieldNames = new Set();
    for (const row of rows) {
        for (const key of Object.keys(row)) {
            if (!metaKeys.has(key) && key !== '_time') fieldNames.add(key);
        }
    }
    const fields = Array.from(fieldNames).sort();
    const header = ['timestamp', ...fields].join(',');
    const csvRows = rows.map(row => {
        const values = fields.map(f => { const v = row[f]; return (v == null) ? '' : v; });
        return [row._time || '', ...values].join(',');
    });
    return header + '\n' + csvRows.join('\n') + '\n';
}

/**
 * Utility interna: esegue una Flux query e restituisce le righe come array di oggetti.
 */
function runFlux(fluxQuery) {
    const rows = [];
    return new Promise((resolve, reject) => {
        querying.queryRows(fluxQuery, {
            next(row, tableMeta) { rows.push(tableMeta.toObject(row)); },
            error: reject,
            complete() { resolve(rows); },
        });
    });
}

/**
 * Elenca tutte le sessioni presenti in InfluxDB, con primo e ultimo timestamp.
 * Sorgente di verità: tag sensor + session sul measurement "logs".
 * @param {string} [lookback='-5y'] - range di ricerca (es. '-365d', '-5y')
 * @returns {Promise<Array<{session, sensor, startTime, endTime}>>}
 */
async function listInfluxSessions(lookback = '-5y') {
    const base = `
        from(bucket: "${sessionBucket}")
            |> range(start: ${lookback})
            |> filter(fn: (r) => r._measurement == "logs")
            |> group(columns: ["sensor", "session"])
    `;
    const [firstRows, lastRows] = await Promise.all([
        runFlux(base + '|> first() |> keep(columns: ["_time", "sensor", "session"])'),
        runFlux(base + '|> last()  |> keep(columns: ["_time", "sensor", "session"])'),
    ]);

    const map = {};
    firstRows.forEach(r => {
        if (!r.session) return;
        map[r.session] = { session: r.session, sensor: r.sensor, startTime: r._time };
    });
    lastRows.forEach(r => {
        if (map[r.session]) map[r.session].endTime = r._time;
    });

    return Object.values(map).sort((a, b) => new Date(b.startTime) - new Date(a.startTime));
}

async function checkInflux() {
    try {
        const result = await querying.collectRows(
            `from(bucket: "boat") |> range(start: -1s) |> limit(n:1)`
        );
        console.log('InfluxDB: OK');
        return { ok: true };
    } catch (error) {
        console.error('InfluxDB check failed:', {
            message: error.message,
            statusCode: error.statusCode ?? 'N/A',
            body: error.body ?? 'N/A'
        });
        return false
    }
}

module.exports = {
    write: append,
    writeBatch,
    query,
    listInfluxSessions,
    querySessionHistory,
    exportSessionCSV,
    checkInflux
}

