import path from 'node:path';
import crypto from 'node:crypto';
import { HttpError } from '../middleware/errors.js';
import {
  ensureRouteDirectories,
  moveUploadedFile,
  removeStoredFile,
  removeUpload,
  resolveStoredFile
} from './storage.js';

const IMAGE_MIME_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
const AUDIO_MIME_TYPES = new Set([
  'audio/mpeg',
  'audio/mp4',
  'audio/x-m4a',
  'audio/aac',
  'audio/ogg',
  'audio/wav',
  'audio/webm'
]);

const uniqueFilename = file => `${crypto.randomUUID()}.${file.detectedExtension}`;
const pictureUrl = (routeId, poiId, pictureId) => `/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}`;
const audioUrl = (routeId, poiId) => `/api/routes/${routeId}/pois/${poiId}/audio`;

function optionalSequence(value, fallback) {
  if (value == null || value === '') return fallback;
  const parsed = Number.parseInt(value, 10);
  if (!Number.isInteger(parsed) || parsed < 0) {
    throw new HttpError(400, 'sequence muss eine nichtnegative Ganzzahl sein.');
  }
  return parsed;
}

function poiOnRoute(db, routeId, poiId) {
  const poi = db.prepare(`
    SELECT p.id, p.route_id, p.title, p.audio_path, p.updated_at
    FROM pois p
    JOIN routes r ON r.id = p.route_id
    WHERE p.id = ? AND p.route_id = ? AND r.status = 'active'
  `).get(poiId, routeId);
  if (!poi) throw new HttpError(404, 'POI auf dieser Strecke nicht gefunden.');
  return poi;
}

function validateUpload(file, kind) {
  if (!file) throw new HttpError(400, `Eine ${kind === 'picture' ? 'Bilddatei' : 'Audiodatei'} ist erforderlich.`);

  const baseMime = String(file.detectedMime || file.mimetype || '').split(';', 1)[0].toLowerCase();
  const valid = kind === 'picture'
    ? IMAGE_MIME_TYPES.has(baseMime)
    : AUDIO_MIME_TYPES.has(baseMime);

  if (!valid || !file.detectedExtension) {
    removeUpload(file);
    throw new HttpError(415, kind === 'picture'
      ? 'Der Dateiinhalt ist keine unterstützte Bilddatei.'
      : 'Der Dateiinhalt ist keine unterstützte Audiodatei.');
  }
}

function pictureFromRow(row) {
  return {
    id: row.id,
    routeId: row.route_id,
    poiId: row.poi_id,
    poiTitle: row.poi_title,
    caption: row.caption,
    sequence: row.sequence,
    url: pictureUrl(row.route_id, row.poi_id, row.id),
    createdAt: row.created_at
  };
}

const pictureSelect = `
  SELECT i.id, i.poi_id, p.route_id, p.title AS poi_title,
         i.path, i.caption, i.sequence, i.created_at
  FROM poi_images i
  JOIN pois p ON p.id = i.poi_id
  JOIN routes r ON r.id = p.route_id
`;

export function listPictures(db, routeId, poiId) {
  poiOnRoute(db, routeId, poiId);
  return db.prepare(`${pictureSelect}
    WHERE p.route_id = ? AND p.id = ? AND r.status = 'active'
    ORDER BY i.sequence, i.id`)
    .all(routeId, poiId)
    .map(pictureFromRow);
}

function pictureRow(db, routeId, poiId, pictureId) {
  poiOnRoute(db, routeId, poiId);
  const row = db.prepare(`${pictureSelect}
    WHERE p.route_id = ? AND p.id = ? AND i.id = ? AND r.status = 'active'`)
    .get(routeId, poiId, pictureId);
  if (!row) throw new HttpError(404, 'Bild für diesen POI nicht gefunden.');
  return row;
}

export function getPicture(db, routeId, poiId, pictureId) {
  return pictureFromRow(pictureRow(db, routeId, poiId, pictureId));
}

export function getPictureFile(db, routeId, poiId, pictureId) {
  const row = pictureRow(db, routeId, poiId, pictureId);
  return {
    absolutePath: resolveStoredFile(row.path),
    filename: path.basename(row.path)
  };
}

export function createPicture(db, routeId, poiId, fields, file) {
  let relativePath;
  try {
    poiOnRoute(db, routeId, poiId);
    validateUpload(file, 'picture');

    const sequence = optionalSequence(fields.sequence,
      db.prepare('SELECT COALESCE(MAX(sequence), -1) + 1 AS value FROM poi_images WHERE poi_id = ?').get(poiId).value);
    const caption = String(fields.caption ?? '');
    const root = ensureRouteDirectories(routeId);

    relativePath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file)));
    const result = db.prepare('INSERT INTO poi_images (poi_id, path, caption, sequence) VALUES (?, ?, ?, ?)')
      .run(poiId, relativePath, caption, sequence);
    return getPicture(db, routeId, poiId, Number(result.lastInsertRowid));
  } catch (error) {
    if (relativePath) removeStoredFile(relativePath);
    else removeUpload(file);
    throw error;
  }
}

export function updatePicture(db, routeId, poiId, pictureId, fields, file) {
  let newPath;
  let oldPath;
  try {
    const existing = getPicture(db, routeId, poiId, pictureId);
    const row = pictureRow(db, routeId, poiId, pictureId);
    oldPath = row.path;
    const caption = fields.caption == null ? existing.caption : String(fields.caption);
    const sequence = optionalSequence(fields.sequence, existing.sequence);

    newPath = row.path;
    if (file) {
      validateUpload(file, 'picture');
      const root = ensureRouteDirectories(routeId);
      newPath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file)));
    }

    db.prepare('UPDATE poi_images SET path = ?, caption = ?, sequence = ? WHERE id = ? AND poi_id = ?')
      .run(newPath, caption, sequence, pictureId, poiId);

    if (file && newPath !== oldPath) removeStoredFile(oldPath);
    return getPicture(db, routeId, poiId, pictureId);
  } catch (error) {
    if (file) {
      if (newPath && newPath !== oldPath) removeStoredFile(newPath);
      else removeUpload(file);
    }
    throw error;
  }
}

export function deletePicture(db, routeId, poiId, pictureId) {
  const picture = getPicture(db, routeId, poiId, pictureId);
  const row = pictureRow(db, routeId, poiId, pictureId);
  db.prepare('DELETE FROM poi_images WHERE id = ? AND poi_id = ?').run(pictureId, poiId);
  removeStoredFile(row.path);
  return { id: picture.id, routeId: picture.routeId, poiId: picture.poiId, deleted: true };
}

function audioFromPoi(poi) {
  return {
    routeId: poi.route_id,
    poiId: poi.id,
    poiTitle: poi.title,
    url: audioUrl(poi.route_id, poi.id),
    updatedAt: poi.updated_at
  };
}

export function getAudio(db, routeId, poiId) {
  const poi = poiOnRoute(db, routeId, poiId);
  if (!poi.audio_path) throw new HttpError(404, 'Audiodatei für diesen POI nicht gefunden.');
  return audioFromPoi(poi);
}

export function getAudioFile(db, routeId, poiId) {
  const poi = poiOnRoute(db, routeId, poiId);
  if (!poi.audio_path) throw new HttpError(404, 'Audiodatei für diesen POI nicht gefunden.');
  return {
    absolutePath: resolveStoredFile(poi.audio_path),
    filename: path.basename(poi.audio_path)
  };
}

function storeAudio(db, routeId, poiId, file, { requireAbsent, requireExisting }) {
  let relativePath;
  let oldPath;
  try {
    validateUpload(file, 'audio');
    const poi = poiOnRoute(db, routeId, poiId);
    oldPath = poi.audio_path;

    if (requireAbsent && poi.audio_path) {
      throw new HttpError(409, 'Für diesen POI ist bereits eine Audiodatei hinterlegt. Verwende PUT zum Ersetzen.');
    }
    if (requireExisting && !poi.audio_path) {
      throw new HttpError(404, 'Für diesen POI ist noch keine Audiodatei hinterlegt. Verwende POST zum Anlegen.');
    }

    const root = ensureRouteDirectories(routeId);
    relativePath = moveUploadedFile(file, path.join(root, 'audio', uniqueFilename(file)));
    db.prepare('UPDATE pois SET audio_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND route_id = ?')
      .run(relativePath, poiId, routeId);

    if (oldPath && oldPath !== relativePath) removeStoredFile(oldPath);
    return getAudio(db, routeId, poiId);
  } catch (error) {
    if (relativePath && relativePath !== oldPath) removeStoredFile(relativePath);
    else removeUpload(file);
    throw error;
  }
}

export function createAudio(db, routeId, poiId, file) {
  return storeAudio(db, routeId, poiId, file, { requireAbsent: true, requireExisting: false });
}

export function updateAudio(db, routeId, poiId, file) {
  return storeAudio(db, routeId, poiId, file, { requireAbsent: false, requireExisting: true });
}

export function deleteAudio(db, routeId, poiId) {
  const audio = getAudio(db, routeId, poiId);
  const poi = poiOnRoute(db, routeId, poiId);
  db.prepare('UPDATE pois SET audio_path = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND route_id = ?')
    .run(poiId, routeId);
  removeStoredFile(poi.audio_path);
  return { routeId: audio.routeId, poiId: audio.poiId, deleted: true };
}
