import { Router, Request, Response, NextFunction } from 'express';
import multer from 'multer';
import { locationsService } from './locations.service';
import {
  createLocationSchema,
  updateLocationSchema,
  listLocationsSchema,
  geocodeAddressSchema,
  bulkDeleteSchema,
  reverseGeocodeSchema,
  bulkImportSchema,
  boundsQuerySchema,
} from './locations.schemas';
import { parseGeoJsonPolygon } from '../../../utils/spatial';
import { prisma } from '../../../config/database';
import { validate } from '../../../middleware/validate';
import { authenticate } from '../../../middleware/auth.middleware';
import { requireRole } from '../../../middleware/rbac.middleware';
import { MAP_ROLES } from '../../../utils/roles';

// Multer config for CSV upload (memory storage, 10MB limit)
const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 10 * 1024 * 1024 },
  fileFilter: (_req, file, cb) => {
    if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
      cb(null, true);
    } else {
      cb(new Error('Only CSV files are allowed'));
    }
  },
});

// Multer config for bulk import (100MB limit for large NAR files)
const bulkUpload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 100 * 1024 * 1024 },
  fileFilter: (_req, file, cb) => {
    if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
      cb(null, true);
    } else {
      cb(new Error('Only CSV files are allowed'));
    }
  },
});

// --- Admin Router ---
const adminRouter = Router();
adminRouter.use(authenticate);
adminRouter.use(requireRole(...MAP_ROLES));

// GET /api/map/locations — list with pagination + filters
adminRouter.get(
  '/',
  validate(listLocationsSchema, 'query'),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await locationsService.findAll(req.query as any);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/map/locations/stats — location statistics
adminRouter.get(
  '/stats',
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const stats = await locationsService.getStats();
      res.json(stats);
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/map/locations/export-csv — export as CSV download
adminRouter.get(
  '/export-csv',
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const csv = await locationsService.exportToCsv();
      res.setHeader('Content-Type', 'text/csv');
      res.setHeader('Content-Disposition', 'attachment; filename=locations.csv');
      res.send(csv);
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/map/locations/all — all geocoded locations for admin map
adminRouter.get(
  '/all',
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const bounds = req.query.minLat ? {
        minLat: parseFloat(req.query.minLat as string),
        maxLat: parseFloat(req.query.maxLat as string),
        minLng: parseFloat(req.query.minLng as string),
        maxLng: parseFloat(req.query.maxLng as string),
      } : undefined;

      const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 5000;

      const locations = await locationsService.getAllForMap(bounds, limit);

      // Add header if we hit the limit
      if (locations.length === limit) {
        res.setHeader('X-Location-Limit-Hit', 'true');
      }

      res.json(locations);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/reverse-geocode — reverse geocode lat/lng to address
adminRouter.post(
  '/reverse-geocode',
  validate(reverseGeocodeSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await locationsService.reverseGeocode(req.body.latitude, req.body.longitude);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/import-bulk — bulk import (NAR or standard CSV)
adminRouter.post(
  '/import-bulk',
  bulkUpload.single('file'),
  async (req: Request, res: Response, next: NextFunction) => {
    // Set 5-minute timeout for large files
    req.setTimeout(5 * 60 * 1000);
    res.setTimeout(5 * 60 * 1000);
    try {
      if (!req.file) {
        res.status(400).json({ error: { message: 'No file uploaded', code: 'NO_FILE' } });
        return;
      }

      const parsed = bulkImportSchema.parse(req.body);

      // Build filters based on filterType
      const filters: {
        cutPolygon?: number[][][];
        bounds?: { north: number; south: number; east: number; west: number };
        city?: string;
        province?: string;
      } = {};

      if (parsed.filterType === 'cut' && parsed.cutId) {
        const cut = await prisma.cut.findUnique({ where: { id: parsed.cutId } });
        if (cut) {
          try {
            filters.cutPolygon = parseGeoJsonPolygon(cut.geojson);
          } catch { /* ignore invalid polygon */ }
        }
      } else if (parsed.filterType === 'mapArea') {
        // Load map settings and compute approximate bounding box
        const settings = await prisma.mapSettings.findFirst();
        if (settings?.latitude && settings?.longitude && settings?.zoom) {
          const lat = parseFloat(settings.latitude.toString());
          const lng = parseFloat(settings.longitude.toString());
          const zoom = settings.zoom;
          // Approximate visible area for a ~1200x800 viewport
          const degreesPerTile = 360 / Math.pow(2, zoom);
          const halfLng = (1200 / 256 / 2) * degreesPerTile;
          const halfLat = (800 / 256 / 2) * degreesPerTile;
          filters.bounds = {
            north: lat + halfLat,
            south: lat - halfLat,
            east: lng + halfLng,
            west: lng - halfLng,
          };
        }
      } else if (parsed.filterType === 'city' && parsed.filterCity) {
        filters.city = parsed.filterCity;
      } else if (parsed.filterType === 'province' && parsed.filterProvince) {
        filters.province = parsed.filterProvince;
      }

      const result = await locationsService.importBulk(req.file.buffer, req.user!.id, parsed, filters);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/map/locations/:id — get single location
adminRouter.get(
  '/:id',
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const id = req.params.id as string;
      const location = await locationsService.findById(id);
      res.json(location);
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/map/locations/:id/history — get location edit history
adminRouter.get(
  '/:id/history',
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const id = req.params.id as string;
      const page = req.query.page ? parseInt(req.query.page as string, 10) : 1;
      const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 20;
      const result = await locationsService.getHistory(id, page, limit);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations — create location
adminRouter.post(
  '/',
  validate(createLocationSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const location = await locationsService.create(req.body, req.user!.id);
      res.status(201).json(location);
    } catch (err) {
      next(err);
    }
  }
);

// PUT /api/map/locations/:id — update location
adminRouter.put(
  '/:id',
  validate(updateLocationSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const id = req.params.id as string;
      const location = await locationsService.update(id, req.body, req.user!.id);
      res.json(location);
    } catch (err) {
      next(err);
    }
  }
);

// DELETE /api/map/locations/:id — delete location
adminRouter.delete(
  '/:id',
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const id = req.params.id as string;
      await locationsService.delete(id);
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/bulk-delete — bulk delete
adminRouter.post(
  '/bulk-delete',
  validate(bulkDeleteSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await locationsService.bulkDelete(req.body.ids);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/geocode — geocode an address
adminRouter.post(
  '/geocode',
  validate(geocodeAddressSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await locationsService.geocodeAddress(req.body.address);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/geocode-missing — geocode all ungeocoded locations
adminRouter.post(
  '/geocode-missing',
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await locationsService.geocodeMissing();
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/map/locations/import-csv — upload + import CSV
adminRouter.post(
  '/import-csv',
  upload.single('file'),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      if (!req.file) {
        res.status(400).json({ error: { message: 'No file uploaded', code: 'NO_FILE' } });
        return;
      }
      const result = await locationsService.importFromCsv(req.file.buffer, req.user!.id);
      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

// --- Public Router ---
const publicRouter = Router();

// GET /api/map/locations/public — aggregated heatmap (no PII, ~1.1km buckets)
publicRouter.get(
  '/public',
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const bounds = req.query.minLat ? {
        minLat: parseFloat(req.query.minLat as string),
        maxLat: parseFloat(req.query.maxLat as string),
        minLng: parseFloat(req.query.minLng as string),
        maxLng: parseFloat(req.query.maxLng as string),
      } : undefined;

      const heatmap = await locationsService.getPublicHeatmap(bounds);

      if (heatmap.points.length === 10000) {
        res.setHeader('X-Location-Bucket-Limit-Hit', 'true');
      }

      res.json(heatmap);
    } catch (err) {
      next(err);
    }
  }
);

export { adminRouter as locationsAdminRouter, publicRouter as locationsPublicRouter };
