const express = require('express');
const cors = require('cors');
// Polyfill for shpjs which expects self.DecompressionStream
global.self = global;
const proj4 = require('proj4');
const path = require('path');
const fs = require('fs');
const https = require('https');

const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.static('dist'));

// Define Projections
proj4.defs("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
proj4.defs("EPSG:4326", "+proj=longlat +datum=WGS84 +no_defs");

const sourceProj = "EPSG:25832";
const destProj = "EPSG:4326";

const ALKIS_WFS_URL = 'https://www.wfs.nrw.de/geobasis/wfs_nw_alkis_vereinfacht';
const SHP_PATH = path.join(__dirname, 'public/Daten/260130_Wohngebäude.shp');

app.get('/api/buildings', async (req, res) => {
    let { bbox, limit } = req.query;

    // Quick test endpoint via browser simply loading /api/buildings
    if (!bbox) return res.send('Worker is ready. Use ?bbox=minLon,minLat,maxLon,maxLat');

    const bounds = bbox.split(',').map(Number);
    if (bounds.length !== 4) return res.status(400).json({ error: 'Invalid bbox' });

    console.time("Query");

    // 1. Project Query BBOX to UTM32
    const corners = [
        [bounds[0], bounds[1]], [bounds[2], bounds[3]],
        [bounds[0], bounds[3]], [bounds[2], bounds[1]]
    ].map(c => proj4(destProj, sourceProj, c));

    const minX = Math.min(...corners.map(c => c[0]));
    const maxX = Math.max(...corners.map(c => c[0]));
    const minY = Math.min(...corners.map(c => c[1]));
    const maxY = Math.max(...corners.map(c => c[1]));

    const queryBbox = [minX - 50, minY - 50, maxX + 50, maxY + 50];
    const maxFeatures = limit ? parseInt(limit) : 2000;
    const results = [];

    // 2. Buffered Scanning
    const BUFFER_SIZE = 10 * 1024 * 1024; // 10MB chunk

    // We open the file and read large chunks.
    // We maintain a "buffer cursor" and a "file position".

    let fd;
    try {
        fd = fs.openSync(SHP_PATH, 'r');
        const fileSize = fs.statSync(SHP_PATH).size;

        let buf = Buffer.alloc(BUFFER_SIZE);
        // Initial Read: Skip 100 bytes header
        let filePos = 100;
        let bytesRead = fs.readSync(fd, buf, 0, BUFFER_SIZE, filePos);
        let bufPos = 0; // Position within current buffer

        while (filePos < fileSize && results.length < maxFeatures && bytesRead > 0) {
            // Need at least 8 bytes for Record Header
            if (bufPos + 8 > bytesRead) {
                // Not enough bytes left in buffer for header. 
                // Move remaining to start, read more.
                // Or simply: Refill logic.
                // Simplest: If near end, just read a new chunk from filePos.
                // But filePos is tricky if we processed partial buffer.
                // Let's track absolute `currentRecordPos` = filePos + bufPos
            }

            // SIMPLER LOGIC:
            // Valid data is in buf[bufPos ... bytesRead]
            const remaining = bytesRead - bufPos;

            if (remaining < 8) {
                // Fetch next chunk
                // Determine where we are in file: filePos + bufPos
                filePos += bufPos;
                bytesRead = fs.readSync(fd, buf, 0, BUFFER_SIZE, filePos);
                bufPos = 0;
                if (bytesRead < 8) break; // EOF
            }

            // Read Record Header (8 bytes)
            // Record Num (4 bytes BE) - ignore
            const recLenWords = buf.readInt32BE(bufPos + 4);
            const recLenBytes = recLenWords * 2;

            // Check if we have the full record content in buffer
            // 8 header + recLenBytes content
            const totalRecSize = 8 + recLenBytes;

            if (bufPos + totalRecSize > bytesRead) {
                // Record spans across buffer boundary (or is larger than buffer remaining)
                // Check if it's larger than entire buffer?
                if (totalRecSize > BUFFER_SIZE) {
                    // Huge record. Rare for buildings. Skip or alloc special.
                    // Just skip logic:
                    filePos += bufPos + totalRecSize;
                    // Refill
                    bytesRead = fs.readSync(fd, buf, 0, BUFFER_SIZE, filePos);
                    bufPos = 0;
                    continue;
                }

                // Move pointer to filePos += bufPos
                filePos += bufPos;
                // Read fresh chunk starting at this record
                bytesRead = fs.readSync(fd, buf, 0, BUFFER_SIZE, filePos);
                bufPos = 0;

                if (bytesRead < totalRecSize) break; // Should not happen unless EOF
            }

            const recOffset = filePos + bufPos;

            // Now buf[bufPos] is start of header.
            // buf[bufPos+8] is start of content.
            const contentOffset = bufPos + 8;

            // Parse BBox (32 bytes) at Content+4 (Type)
            // Type is at contentOffset (4 bytes LE)
            const type = buf.readInt32LE(contentOffset);

            // BBox: offset + 4
            const bXmin = buf.readDoubleLE(contentOffset + 4);
            const bYmin = buf.readDoubleLE(contentOffset + 12);
            const bXmax = buf.readDoubleLE(contentOffset + 20);
            const bYmax = buf.readDoubleLE(contentOffset + 28);

            const disjoint = (bXmin > queryBbox[2] || bXmax < queryBbox[0] || bYmin > queryBbox[3] || bYmax < queryBbox[1]);

            if (!disjoint) {
                // Match! Parse Geometry.
                // Only if Type 5 (Polygon)
                if (type === 5) {
                    const numParts = buf.readInt32LE(contentOffset + 36);
                    const numPoints = buf.readInt32LE(contentOffset + 40);

                    if (numPoints > 0 && numPoints < 10000) {
                        const parts = [];
                        for (let i = 0; i < numParts; i++) {
                            parts.push(buf.readInt32LE(contentOffset + 44 + i * 4));
                        }
                        parts.push(numPoints);

                        const pointsOffset = contentOffset + 44 + numParts * 4;
                        const coords = [];

                        for (let p = 0; p < numParts; p++) {
                            const start = parts[p];
                            const end = parts[p + 1];
                            const ring = [];
                            for (let k = start; k < end; k++) {
                                const px = buf.readDoubleLE(pointsOffset + k * 16);
                                const py = buf.readDoubleLE(pointsOffset + k * 16 + 8);
                                const [lon, lat] = proj4(sourceProj, destProj, [px, py]);
                                ring.push([lon, lat]);
                            }
                            coords.push(ring);
                        }

                        results.push({
                            type: "Feature",
                            properties: { id: "SHP_" + recOffset },
                            geometry: { type: "Polygon", coordinates: coords }
                        });
                    }
                }
            }

            // Advance
            bufPos += totalRecSize;
        }

    } catch (e) {
        console.error("Fehler beim Scannen des Buffers:", e);
    } finally {
        if (fd) fs.closeSync(fd);
    }

    console.timeEnd("Query");
    console.log(`BBox-Abfrage fand ${results.length} Gebäude`);
    res.json({ type: "FeatureCollection", features: results });
});

/**
 * API: Ruft detaillierte ALKIS-Informationen für einen Punkt ab (GML-Parsing)
 */
app.get('/api/alkis-info', async (req, res) => {
    const { lat, lng } = req.query;
    if (!lat || !lng) return res.status(400).json({ error: 'Koordinaten fehlen' });

    const lats = parseFloat(lat);
    const lngs = parseFloat(lng);
    const size = 0.0001;
    const bbox = `${lats - size},${lngs - size},${lats + size},${lngs + size},urn:ogc:def:crs:EPSG::4326`;
    const url = `${ALKIS_WFS_URL}?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=ave:Flurstueck&BBOX=${bbox}&COUNT=1`;

    https.get(url, (wfsRes) => {
        let data = '';
        wfsRes.on('data', (chunk) => data += chunk);
        wfsRes.on('end', () => {
            try {
                // Einfaches Regex-basiertes Parsing für GML (da kein schwerer XML-Parser installiert ist)
                const getTag = (tag) => {
                    const match = data.match(new RegExp(`<${tag}[^>]*>(.*?)<\/${tag}>`, 's'));
                    return match ? match[1].trim() : null;
                };

                const info = {
                    gemarkung: getTag('gemarkung'),
                    gemeinde: getTag('gemeinde'),
                    kreis: getTag('kreis'),
                    regbezirk: getTag('regbezirk'),
                    flur: getTag('flur'),
                    flstnrzae: getTag('flstnrzae'),
                    flstnrnen: getTag('flstnrnen'),
                    flaeche: getTag('flaeche'),
                    aktualit: getTag('aktualit'),
                    lagebeztxt: getTag('lagebeztxt'),
                    flstkennz: getTag('flstkennz')
                };

                if (!info.flstkennz) {
                    return res.json({ found: false });
                }

                res.json({ found: true, properties: info });
            } catch (err) {
                res.status(500).json({ error: 'Parsing Fehler', details: err.message });
            }
        });
    }).on('error', (e) => {
        res.status(500).json({ error: 'WFS Fehler', details: e.message });
    });
});

/**
 * API: Ruft ALKIS-Nutzungsinformationen für einen Punkt ab
 */
app.get('/api/alkis-usage', async (req, res) => {
    const { lat, lng } = req.query;
    if (!lat || !lng) return res.status(400).json({ error: 'Koordinaten fehlen' });

    const lats = parseFloat(lat);
    const lngs = parseFloat(lng);
    const size = 0.00001; // Sehr kleiner BBOX für präzise Punkt-Abfrage (~1m)
    const bbox = `${lats - size},${lngs - size},${lats + size},${lngs + size},urn:ogc:def:crs:EPSG::4326`;
    const url = `${ALKIS_WFS_URL}?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=ave:Nutzung&BBOX=${bbox}&COUNT=1`;

    https.get(url, (wfsRes) => {
        let data = '';
        wfsRes.on('data', (chunk) => data += chunk);
        wfsRes.on('end', () => {
            try {
                // Regex-Parsing für nutzart und bez
                const nutzartMatch = data.match(/<nutzart[^>]*>(.*?)<\/nutzart>/s);
                const bezMatch = data.match(/<bez[^>]*>(.*?)<\/bez>/s);

                const nutzart = nutzartMatch ? nutzartMatch[1].trim() : null;
                const bez = bezMatch ? bezMatch[1].trim() : null;

                if (!nutzart && !bez) {
                    return res.json({ found: false });
                }

                res.json({ found: true, nutzart, bez });
            } catch (err) {
                res.status(500).json({ error: 'Parsing Fehler', details: err.message });
            }
        });
    }).on('error', (e) => {
        res.status(500).json({ error: 'WFS Fehler', details: e.message });
    });
});

/**
 * API: Ruft Windenergieanlagen in der Nähe ab (WFS Proxy)
 */
app.get('/api/wind-turbines', async (req, res) => {
    const { lat, lng } = req.query;
    if (!lat || !lng) return res.status(400).json({ error: 'Koordinaten fehlen' });

    const lats = parseFloat(lat);
    const lngs = parseFloat(lng);
    const size = 0.05; // ~5.5km BBOX für absolute Sicherheit
    const bbox = `${lats - size},${lngs - size},${lats + size},${lngs + size},urn:ogc:def:crs:EPSG::4326`;

    // Wir nutzen WFS 2.0.0 da dieser stabil Daten liefert
    const url = `https://www.wfs.nrw.de/umwelt/erneuerbare_energien_wfs?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=erneuerbare_energien_wfs:WFS_Windenergie&BBOX=${bbox}&COUNT=100`;

    console.log(`[WindProxy] Fetching: ${url}`);

    https.get(url, (wfsRes) => {
        console.log(`[WindProxy] Status: ${wfsRes.statusCode}`);
        let data = '';
        wfsRes.on('data', (chunk) => data += chunk);
        wfsRes.on('end', () => {
            try {
                if (data.includes('ExceptionReport')) {
                    console.error(`[WindProxy] WFS Exception: ${data.substring(0, 500)}`);
                    return res.status(500).json({ error: 'WFS Exception', details: 'Der Dienst hat einen Fehler gemeldet.' });
                }

                const features = [];
                // Robustes Splitten (Regex handles attributes in member tag)
                const blocks = data.split(/<wfs:member[^>]*>/);

                for (let i = 1; i < blocks.length; i++) {
                    const block = blocks[i];

                    // Extrahiere UTM Koordinaten aus gml:pos
                    const posMatch = block.match(/<gml:pos[^>]*>(.*?)<\/gml:pos>/);
                    const ibjahrMatch = block.match(/<erneuerbare_energien_wfs:ibjahr[^>]*>(.*?)<\/erneuerbare_energien_wfs:ibjahr>/);

                    if (posMatch) {
                        const coords = posMatch[1].trim().split(/\s+/).map(Number);
                        const [lon, lat] = proj4(sourceProj, destProj, [coords[0], coords[1]]);
                        features.push({
                            type: 'Feature',
                            geometry: {
                                type: 'Point',
                                coordinates: [lon, lat] // [Lon, Lat] in WGS84
                            },
                            properties: {
                                ibjahr: ibjahrMatch ? ibjahrMatch[1].trim() : "Unbekannt"
                            }
                        });
                    }
                }
                console.log(`[WindProxy] Found ${features.length} turbines for ${lat},${lng}`);
                res.json({ features });
            } catch (err) {
                res.status(500).json({ error: 'Parsing Fehler', details: err.message });
            }
        });
    }).on('error', (e) => {
        res.status(500).json({ error: 'WFS Fehler', details: e.message });
    });
});

// --- Logik für Potenzialflächen & Regionalplanung ---
const POTENTIAL_ZIP_PATH = path.join(__dirname, 'public/daten/Potenzialkarten-Windenergieanlagen-NRW_EPSG25832_Shape.zip');
const PLANNING_ZIP_PATH = path.join(__dirname, 'public/daten/Windenergiebereiche_NRW.zip');

let potentialFeatures = [];
let potentialLoaded = false;
let planningFeatures = [];
let planningLoaded = false;
let planningError = null;
let turf = null;

/**
 * Prüft, ob ein Punkt innerhalb eines Polygons liegt (unter Verwendung von Turf.js)
 */
function checkPointInFeature(pt, feature) {
    if (!turf) return false;
    if (!feature || !feature.geometry) return false;
    try {
        const point = turf.point(pt); // pt muss [lng, lat] sein
        if (feature.geometry.type === 'Polygon' || feature.geometry.type === 'MultiPolygon') {
            return turf.booleanPointInPolygon(point, feature);
        }
    } catch (e) { }
    return false;
}

/**
 * Lädt die notwendigen Bibliotheken und Geodaten beim Serverstart
 */
(async () => {
    try {
        console.log("Lade Bibliotheken & Daten...");
        const { default: shp } = await import('shpjs');
        const turfModule = await import('@turf/turf');
        turf = turfModule.default || turfModule;

        // 1. Regionalplanung (Windenergiebereiche) laden
        if (fs.existsSync(PLANNING_ZIP_PATH)) {
            console.log(`[${new Date().toISOString()}] Lade Planungsdaten von: ${PLANNING_ZIP_PATH}`);
            try {
                const buffer = fs.readFileSync(PLANNING_ZIP_PATH);
                const geojson = await shp(buffer);

                if (Array.isArray(geojson)) {
                    geojson.forEach(fc => { if (fc.features) planningFeatures.push(...fc.features); });
                } else {
                    planningFeatures = geojson.features;
                }
                planningLoaded = true;
                console.log(`[${new Date().toISOString()}] Planungsdaten geladen: ${planningFeatures.length} Features.`);
            } catch (err) {
                planningError = err.message;
                console.warn("Konnte Planungs-ZIP nicht laden:", err);
            }
        } else {
            console.warn("Planungs-ZIP nicht gefunden:", PLANNING_ZIP_PATH);
            planningError = "Datei nicht gefunden: " + PLANNING_ZIP_PATH;
        }

        // 2. Potenzialkarten laden
        if (fs.existsSync(POTENTIAL_ZIP_PATH)) {
            console.log(`[${new Date().toISOString()}] Lade Potenzialdaten...`);
            const buffer = fs.readFileSync(POTENTIAL_ZIP_PATH);
            const geojson = await shp(buffer);
            if (Array.isArray(geojson)) {
                geojson.forEach(fc => { if (fc.features) potentialFeatures.push(...fc.features); });
            } else {
                potentialFeatures = geojson.features;
            }
            potentialLoaded = true;
            console.log(`[${new Date().toISOString()}] Potenzialdaten geladen: ${potentialFeatures.length} Features.`);
        }

    } catch (e) {
        console.error("Fehler beim Laden der Daten / Libs:", e);
        planningError = "Globaler Ladefehler: " + e.message;
    }
})();

/**
 * API: Prüft, ob ein Standort in einer LANUV-Potenzialfläche liegt
 */
app.get('/api/check-potential', (req, res) => {
    const { lat, lng } = req.query;
    if (!potentialLoaded) return res.json({ result: 'error', message: 'Daten nicht geladen' });
    if (!lat || !lng) return res.json({ result: 'error', message: 'Koordinaten fehlen' });

    try {
        const pt = [parseFloat(lng), parseFloat(lat)];
        if (isNaN(pt[0]) || isNaN(pt[1])) throw new Error("Ungültige Koordinaten");

        let found = false;
        let props = {};

        for (const f of potentialFeatures) {
            if (checkPointInFeature(pt, f)) {
                found = true;
                props = f.properties;
                break;
            }
        }

        res.json({
            found: found,
            properties: found ? props : null
        });
    } catch (e) {
        res.status(500).json({ error: e.toString() });
    }
});

/**
 * API: Prüft, ob ein Standort in einem Windenergiebereich liegt
 */
app.get('/api/check-planning', (req, res) => {
    const { lat, lng } = req.query;
    if (!planningLoaded) return res.json({ found: false, message: 'Daten nicht geladen' });
    if (!lat || !lng) return res.json({ found: false, message: 'Koordinaten fehlen' });

    try {
        const pt = [parseFloat(lng), parseFloat(lat)];
        let found = false;
        let props = {};

        for (const f of planningFeatures) {
            if (checkPointInFeature(pt, f)) {
                found = true;
                props = f.properties;
                break;
            }
        }

        res.json({ found: found, properties: found ? props : null });
    } catch (e) {
        res.status(500).json({ error: e.toString() });
    }
});

/**
 * API: Liefert alle Potenzialflächen als GeoJSON
 */
app.get('/api/potential-data', (req, res) => {
    if (!potentialLoaded) return res.status(503).json({ error: 'Daten noch nicht bereit' });
    res.json({ type: 'FeatureCollection', features: potentialFeatures });
});

/**
 * API: Liefert alle Windenergiebereiche als GeoJSON
 */
app.get('/api/planning-data', (req, res) => {
    if (!planningLoaded) {
        if (planningError) {
            return res.status(500).json({ error: 'Laden der Planungsdaten fehlgeschlagen: ' + planningError });
        }
        return res.status(503).json({ error: 'Daten werden geladen... bitte erneut versuchen.', retry: true });
    }
    res.json({ type: 'FeatureCollection', features: planningFeatures });
});

app.listen(PORT, () => {
    console.log(`Geodaten-Server läuft auf http://localhost:${PORT} (Buffered Mode)`);
});
