const express = require("express");
const db = require("../db/polygons");

const router = express.Router();

// =======================
// Query by bbox/time(/entity)
// =======================
router.get("/", (req, res) => {
    const minLng = Number(req.query.minLng);
    const minLat = Number(req.query.minLat);
    const maxLng = Number(req.query.maxLng);
    const maxLat = Number(req.query.maxLat);

    if (
        !Number.isFinite(minLng) ||
        !Number.isFinite(minLat) ||
        !Number.isFinite(maxLng) ||
        !Number.isFinite(maxLat)
    ) {
        return res.status(400).json({ error: "Missing/invalid bbox" });
    }

    let t = null;
    if (req.query.time !== undefined && req.query.time !== "") {
        const parsed = Number(req.query.time);
        if (!Number.isFinite(parsed)) {
            return res.status(400).json({ error: "Invalid time" });
        }
        t = Math.trunc(parsed);
    }

    let entityId = null;
    try {
        entityId = normalizeEntityId(req.query.entity_id);
    } catch (err) {
        return res.status(err.status || 400).json({ error: err.message });
    }

    const rows = db.prepare(`
        SELECT
            g.*
        FROM geometries g
        WHERE g.is_deleted = 0
          AND g.bbox_max_lng >= ?
          AND g.bbox_min_lng <= ?
          AND g.bbox_max_lat >= ?
          AND g.bbox_min_lat <= ?
          AND (
                ? IS NULL
                OR (
                    (g.time_start IS NULL OR g.time_start <= ?)
                    AND (g.time_end IS NULL OR g.time_end >= ?)
                )
            )
          AND (
                ? IS NULL
                OR EXISTS (
                    SELECT 1
                    FROM entity_geometries eg
                    JOIN entities e
                      ON e.id = eg.entity_id
                     AND e.is_deleted = 0
                    WHERE eg.geometry_id = g.id
                      AND eg.entity_id = ?
                )
            )
    `).all(minLng, maxLng, minLat, maxLat, t, t, t, entityId, entityId);

    const geometryIds = rows.map((row) => String(row.id));
    const linksByGeometryId = loadGeometryLinksByGeometryId(geometryIds);

    res.json({
        type: "FeatureCollection",
        features: rows.map((row) => {
            const linkedEntities = linksByGeometryId.get(String(row.id)) || [];
            return buildFeatureFromRow(row, linkedEntities);
        }),
    });
});

module.exports = router;

function buildFeatureFromRow(row, linkedEntities = []) {
    const entityIds = linkedEntities.map((entity) => entity.entity_id);
    const primaryEntityId = entityIds[0] || null;
    const orderedEntityIds = entityIds;
    const orderedLinkedEntities = linkedEntities;
    const primaryEntity =
        orderedLinkedEntities.find((entity) => entity.entity_id === primaryEntityId) ||
        orderedLinkedEntities[0] ||
        null;
    const storedGeometryType = normalizeGeometryType(row.type);
    const semanticType = storedGeometryType && !isLegacyLineModeToken(storedGeometryType)
        ? storedGeometryType
        : normalizeGeometryType(primaryEntity?.entity_type_id);

    return {
        type: "Feature",
        properties: {
            id: row.id,
            type: semanticType || null,
            time_start: row.time_start,
            time_end: row.time_end,
            binding: parseBindingIds(row.binding),
            entity_id: primaryEntityId,
            entity_ids: orderedEntityIds,
            entity_name: primaryEntity?.entity_name || null,
            entity_names: orderedLinkedEntities
                .map((entity) => entity.entity_name)
                .filter((name) => typeof name === "string" && name.length > 0),
            entity_type_id: primaryEntity?.entity_type_id || null,
        },
        geometry: JSON.parse(row.draw_geometry),
    };
}

function normalizeGeometryType(value) {
    if (value === undefined || value === null || value === "") return null;
    const normalized = String(value).trim().toLowerCase();
    return normalized.length ? normalized : null;
}

function isLegacyLineModeToken(value) {
    return value === "line" || value === "path";
}

function normalizeEntityId(value) {
    if (value === undefined || value === null || value === "") return null;
    const normalized = String(value).trim();
    return normalized || null;
}

function normalizeBindingIds(rawBinding) {
    if (rawBinding === undefined || rawBinding === null || rawBinding === "") {
        return [];
    }

    if (!Array.isArray(rawBinding)) {
        throw createValidationError("binding must be an array");
    }

    const deduped = [];
    const seen = new Set();

    for (const rawId of rawBinding) {
        if (typeof rawId !== "string" && typeof rawId !== "number") {
            throw createValidationError("binding must contain geometry ids as string/number");
        }

        const normalized = String(rawId).trim();
        if (!normalized || seen.has(normalized)) continue;
        seen.add(normalized);
        deduped.push(normalized);
    }

    return deduped;
}

function parseBindingIds(rawBinding) {
    if (rawBinding === undefined || rawBinding === null || rawBinding === "") {
        return [];
    }

    if (Array.isArray(rawBinding)) {
        try {
            return normalizeBindingIds(rawBinding);
        } catch (_err) {
            return [];
        }
    }

    if (typeof rawBinding === "string") {
        try {
            const parsed = JSON.parse(rawBinding);
            return normalizeBindingIds(parsed);
        } catch (_err) {
            const plain = rawBinding.trim();
            return plain.length ? [plain] : [];
        }
    }

    return [];
}

function loadGeometryLinksByGeometryId(geometryIds) {
    const map = new Map();
    if (!geometryIds.length) return map;

    const placeholders = geometryIds.map(() => "?").join(",");
    const rows = db.prepare(`
        SELECT
            eg.geometry_id,
            eg.entity_id,
            e.name AS entity_name,
            e.type_id AS entity_type_id
        FROM entity_geometries eg
        JOIN entities e
            ON e.id = eg.entity_id
           AND e.is_deleted = 0
        WHERE eg.geometry_id IN (${placeholders})
        ORDER BY eg.rowid ASC
    `).all(...geometryIds);

    for (const row of rows) {
        const geometryId = String(row.geometry_id);
        const current = map.get(geometryId) || [];
        current.push({
            entity_id: row.entity_id,
            entity_name: row.entity_name || null,
            entity_type_id: row.entity_type_id || null,
        });
        map.set(geometryId, current);
    }

    return map;
}

function createValidationError(message, status = 400) {
    const err = new Error(message);
    err.status = status;
    return err;
}
