const express = require('express');
const cors = require('cors');
const { Pool } = require('pg');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3051;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL || 'postgresql://line_of_sight:line_of_sight_pass@postgres:5432/line_of_sight'
});

app.use(cors());
app.use(express.json());

const calculateDestination = (lat, lon, bearing, distance) => {
  const R = 6371;
  const brng = (bearing * Math.PI) / 180;
  const φ1 = (lat * Math.PI) / 180;
  const λ1 = (lon * Math.PI) / 180;
  const δ = distance / R;

  const φ2 = Math.asin(
    Math.sin(φ1) * Math.cos(δ) +
      Math.cos(φ1) * Math.sin(δ) * Math.cos(brng)
  );
  const λ2 =
    λ1 +
    Math.atan2(
      Math.sin(brng) * Math.sin(δ) * Math.cos(φ1),
      Math.cos(δ) - Math.sin(φ1) * Math.sin(φ2)
    );

  return {
    lat: (φ2 * 180) / Math.PI,
    lon: (((λ2 * 180) / Math.PI + 540) % 360) - 180
  };
};

const haversineKm = (lat1, lon1, lat2, lon2) => {
  const R = 6371;
  const toRad = (d) => d * Math.PI / 180;
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};

app.get('/api/line-of-sight', async (req, res) => {
  const { lat, lon, direction, tolerance } = req.query;

  const startLat = parseFloat(lat) || 51.5074;
  const startLon = parseFloat(lon) || -0.1278;
  const bearing = parseInt(direction) || 0;
  const toleranceKm = parseInt(tolerance) || 50;

  console.log(`Processing request: lat=${startLat}, lon=${startLon}, bearing=${bearing}, tolerance=${toleranceKm}`);

  try {
    const pathPoints = [];
    const totalDistance = 40074;
    const steps = 160;

    for (let i = 0; i <= steps; i++) {
      const dist = (totalDistance * i) / steps;
      pathPoints.push(calculateDestination(startLat, startLon, bearing, dist));
    }

    // Batch water check: a point is "over water" if no city is within 500 km
    const waterChecks = await Promise.all(pathPoints.map(async (p) => {
      const checkQuery = `
        SELECT EXISTS (
          SELECT 1 FROM cities
          WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, 500000)
          LIMIT 1
        ) as has_land;
      `;
      const r = await pool.query(checkQuery, [p.lon, p.lat]);
      return !r.rows[0].has_land;
    }));

    const pathPointsWithWater = pathPoints.map((p, i) => ({
      ...p,
      is_over_water: waterChecks[i]
    }));

    const lineWKT = `LINESTRING(${pathPoints.map(p => `${p.lon} ${p.lat}`).join(',')})`;

    // Top 5 cities per 100 km bin, ranked by population descending
    const query = `
      WITH path AS (
        SELECT ST_GeogFromText($1) as route,
               ST_MakePoint($3, $4)::geography as start_node
      ),
      candidates AS (
        SELECT
          id, name, population, country,
          ST_Y(geom::geometry) as lat,
          ST_X(geom::geometry) as lon,
          ST_Distance(geom, (SELECT route FROM path)) / 1000 as distance_off_line_km,
          ST_Distance(geom, (SELECT start_node FROM path)) / 1000 as distance_from_start_km,
          ST_LineLocatePoint((SELECT route FROM path)::geography::geometry, geom::geometry) as pos_on_line,
          FLOOR(ST_LineLocatePoint((SELECT route FROM path)::geography::geometry, geom::geometry) * 200)::int as bin_200km
        FROM cities
        WHERE ST_DWithin(geom, (SELECT route FROM path), $2 * 1000)
      ),
      ranked AS (
        SELECT *,
          ROW_NUMBER() OVER (PARTITION BY bin_200km ORDER BY population DESC NULLS LAST) as rank_in_bin
        FROM candidates
      )
      SELECT id, name, population, country, lat, lon,
             distance_off_line_km, distance_from_start_km, pos_on_line
      FROM ranked
      WHERE rank_in_bin <= 10
      ORDER BY pos_on_line ASC;
    `;

    const result = await pool.query(query, [lineWKT, toleranceKm, startLon, startLat]);

    // Greedy dynamic deduplication: sort by population desc, accept a city only if
    // it satisfies a distance threshold that depends on its importance (population).
    const byPopulation = [...result.rows].sort((a, b) => (b.population || 0) - (a.population || 0));
    const accepted = [];
    for (const city of byPopulation) {
      const cityPop = city.population || 0;
      const tooClose = accepted.some(s => {
        const dist = haversineKm(s.lat, s.lon, city.lat, city.lon);
        const sPop = s.population || 0;

        // Major hubs (>1M) can be closer (30km) to each other (e.g., Tokyo/Yokohama)
        if (cityPop > 1000000 && sPop > 1000000) return dist < 30;
        
        // Large cities (>100k) need at least 50km space
        if (cityPop > 100000 || sPop > 100000) return dist < 50;
        
        // Smaller towns and villages need 80km space from any other accepted place
        // to prevent clutter in high-density regions (like Europe/East Coast).
        // Isolated towns (Alaska/Outback) will still show up as there's nothing else near them.
        return dist < 80;
      });
      if (!tooClose) accepted.push(city);
    }
    accepted.sort((a, b) => a.pos_on_line - b.pos_on_line);

    res.json({
      success: true,
      data: {
        start_point: { lat: startLat, lon: startLon },
        direction: bearing,
        tolerance_km: toleranceKm,
        conurbations: accepted.map(row => ({
          ...row,
          name: row.name || 'Unknown',
          country: row.country || 'Unknown',
          distance_km: Math.round(row.distance_from_start_km),
          off_line_km: Math.round(row.distance_off_line_km)
        })),
        line_coordinates: pathPointsWithWater
      }
    });
  } catch (err) {
    console.error('Database query error:', err);
    res.status(500).json({ success: false, error: 'Database query failed' });
  }
});

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

if (require.main === module) {
  app.listen(PORT, '0.0.0.0', () => {
    console.log(`Line of Sight Backend running on port ${PORT}`);
  });
}

module.exports = { app, calculateDestination };
