'use strict';

const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const { dataDir } = require('../config');
const { getDb } = require('../db/database');
const logger = require('./logger').child('THUMBNAIL');

const THUMBNAILS_DIR = path.join(dataDir, 'thumbnails');
const CACHE_DIR = path.join(THUMBNAILS_DIR, 'cache');
const MAX_REDIRECTS = 5;

function ensureDirs() {
  fs.mkdirSync(CACHE_DIR, { recursive: true });
  fs.mkdirSync(THUMBNAILS_DIR, { recursive: true });
}

function cacheFilePath(jobId) {
  return path.join(CACHE_DIR, `job-${jobId}.jpg`);
}

function persistentFilePath(jobId) {
  return path.join(THUMBNAILS_DIR, `job-${jobId}.jpg`);
}

function localUrl(jobId) {
  return `/api/thumbnails/job-${jobId}.jpg`;
}

function isLocalUrl(url) {
  return typeof url === 'string' && url.startsWith('/api/thumbnails/');
}

function resolveLocalThumbnailPath(url) {
  const raw = String(url || '').trim();
  const match = raw.match(/^\/api\/thumbnails\/job-(\d+)\.jpg$/i);
  if (!match) {
    return null;
  }
  const jobId = Number(match[1]);
  if (!Number.isFinite(jobId) || jobId <= 0) {
    return null;
  }
  return persistentFilePath(Math.trunc(jobId));
}

function localThumbnailUrlExists(url) {
  const targetPath = resolveLocalThumbnailPath(url);
  if (!targetPath) {
    return false;
  }
  try {
    return fs.existsSync(targetPath);
  } catch (_error) {
    return false;
  }
}

function downloadImage(url, destPath, redirectsLeft = MAX_REDIRECTS) {
  return new Promise((resolve, reject) => {
    if (redirectsLeft <= 0) {
      return reject(new Error('Zu viele Weiterleitungen beim Bild-Download'));
    }

    const proto = url.startsWith('https') ? https : http;
    const file = fs.createWriteStream(destPath);

    const cleanup = () => {
      try { file.destroy(); } catch (_) {}
      try { if (fs.existsSync(destPath)) fs.unlinkSync(destPath); } catch (_) {}
    };

    proto.get(url, { timeout: 15000 }, (res) => {
      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
        res.resume();
        file.close(() => {
          try { if (fs.existsSync(destPath)) fs.unlinkSync(destPath); } catch (_) {}
          downloadImage(res.headers.location, destPath, redirectsLeft - 1).then(resolve).catch(reject);
        });
        return;
      }

      if (res.statusCode !== 200) {
        res.resume();
        cleanup();
        return reject(new Error(`HTTP ${res.statusCode} beim Bild-Download`));
      }

      res.pipe(file);
      file.on('finish', () => file.close(() => resolve()));
      file.on('error', (err) => { cleanup(); reject(err); });
    }).on('error', (err) => {
      cleanup();
      reject(err);
    }).on('timeout', function () {
      this.destroy();
      cleanup();
      reject(new Error('Timeout beim Bild-Download'));
    });
  });
}

/**
 * Lädt das Bild einer extern-URL in den Cache herunter.
 * Wird aufgerufen sobald poster_url bekannt ist (vor Rip-Start).
 * @returns {Promise<string|null>} lokaler Pfad oder null
 */
async function cacheJobThumbnailDetailed(jobId, posterUrl) {
  const normalizedPosterUrl = String(posterUrl || '').trim();
  if (!normalizedPosterUrl || isLocalUrl(normalizedPosterUrl)) {
    return {
      ok: false,
      jobId,
      posterUrl: normalizedPosterUrl || null,
      cachedPath: null,
      error: 'Kein externer Poster-Link vorhanden.'
    };
  }

  try {
    ensureDirs();
    const dest = cacheFilePath(jobId);
    await downloadImage(normalizedPosterUrl, dest);
    logger.info('thumbnail:cached', { jobId, posterUrl: normalizedPosterUrl, dest });
    return {
      ok: true,
      jobId,
      posterUrl: normalizedPosterUrl,
      cachedPath: dest,
      error: null
    };
  } catch (err) {
    const message = String(err?.message || err || 'Bild-Download fehlgeschlagen');
    logger.warn('thumbnail:cache:failed', { jobId, posterUrl: normalizedPosterUrl, error: message });
    return {
      ok: false,
      jobId,
      posterUrl: normalizedPosterUrl,
      cachedPath: null,
      error: message
    };
  }
}

async function cacheJobThumbnail(jobId, posterUrl) {
  const result = await cacheJobThumbnailDetailed(jobId, posterUrl);
  return result?.ok ? result.cachedPath : null;
}

/**
 * Verschiebt das gecachte Bild in den persistenten Ordner.
 * Gibt die lokale API-URL zurück, oder null wenn kein Bild vorhanden.
 * Wird nach erfolgreichem Rip aufgerufen.
 * @returns {string|null} lokale URL (/api/thumbnails/job-{id}.jpg) oder null
 */
function promoteJobThumbnail(jobId) {
  try {
    ensureDirs();
    const src = cacheFilePath(jobId);
    const dest = persistentFilePath(jobId);

    if (fs.existsSync(src)) {
      fs.renameSync(src, dest);
      logger.info('thumbnail:promoted', { jobId, dest });
      return localUrl(jobId);
    }

    // Falls kein Cache vorhanden, aber persistente Datei schon existiert
    if (fs.existsSync(dest)) {
      return localUrl(jobId);
    }

    logger.warn('thumbnail:promote:no-source', { jobId });
    return null;
  } catch (err) {
    logger.warn('thumbnail:promote:failed', { jobId, error: err.message });
    return null;
  }
}

/**
 * Gibt den Pfad zum persistenten Thumbnail-Ordner zurück (für Static-Serving).
 */
function getThumbnailsDir() {
  return THUMBNAILS_DIR;
}

/**
 * Kopiert das persistente Thumbnail von sourceJobId zu targetJobId.
 * Wird bei Rip-Neustart genutzt, damit der neue Job ein eigenes Bild hat
 * und nicht auf die Datei des alten Jobs angewiesen ist.
 * @returns {string|null} neue lokale URL oder null
 */
function copyThumbnail(sourceJobId, targetJobId) {
  try {
    const src = persistentFilePath(sourceJobId);
    if (!fs.existsSync(src)) return null;
    ensureDirs();
    const dest = persistentFilePath(targetJobId);
    fs.copyFileSync(src, dest);
    logger.info('thumbnail:copied', { sourceJobId, targetJobId });
    return localUrl(targetJobId);
  } catch (err) {
    logger.warn('thumbnail:copy:failed', { sourceJobId, targetJobId, error: err.message });
    return null;
  }
}

/**
 * Speichert ein lokal extrahiertes Bild als persistentes Job-Thumbnail.
 * @returns {string|null} lokale URL (/api/thumbnails/job-{id}.jpg) oder null
 */
function storeLocalThumbnail(jobId, sourcePath) {
  try {
    const src = String(sourcePath || '').trim();
    if (!src || !fs.existsSync(src)) {
      return null;
    }
    ensureDirs();
    const dest = persistentFilePath(jobId);
    fs.copyFileSync(src, dest);
    logger.info('thumbnail:stored-local', { jobId, sourcePath: src, dest });
    return localUrl(jobId);
  } catch (err) {
    logger.warn('thumbnail:store-local:failed', { jobId, sourcePath, error: err.message });
    return null;
  }
}

/**
 * Löscht Cache- und persistente Thumbnail-Datei eines Jobs.
 * Wird beim Löschen eines Jobs aufgerufen.
 */
function deleteThumbnail(jobId) {
  for (const filePath of [persistentFilePath(jobId), cacheFilePath(jobId)]) {
    try {
      if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
    } catch (err) {
      logger.warn('thumbnail:delete:failed', { jobId, filePath, error: err.message });
    }
  }
}

/**
 * Migriert bestehende Jobs: lädt alle externen poster_url-Bilder herunter
 * und speichert sie lokal. Läuft beim Start im Hintergrund, sequenziell
 * mit kurzem Delay um externe Server nicht zu überlasten.
 */
async function migrateExistingThumbnails() {
  try {
    ensureDirs();
    const db = await getDb();

    // Alle abgeschlossenen Jobs mit externer poster_url, die noch kein lokales Bild haben
    const jobs = await db.all(
      `SELECT id, poster_url FROM jobs
       WHERE rip_successful = 1
         AND poster_url IS NOT NULL
         AND poster_url != ''
         AND poster_url NOT LIKE '/api/thumbnails/%'
       ORDER BY id ASC`
    );

    if (!jobs.length) {
      logger.info('thumbnail:migrate:nothing-to-do');
      return;
    }

    logger.info('thumbnail:migrate:start', { count: jobs.length });
    let succeeded = 0;
    let failed = 0;

    for (const job of jobs) {
      // Persistente Datei bereits vorhanden? Dann nur DB aktualisieren.
      const dest = persistentFilePath(job.id);
      if (fs.existsSync(dest)) {
        await db.run('UPDATE jobs SET poster_url = ? WHERE id = ?', [localUrl(job.id), job.id]);
        succeeded++;
        continue;
      }

      try {
        await downloadImage(job.poster_url, dest);
        await db.run('UPDATE jobs SET poster_url = ? WHERE id = ?', [localUrl(job.id), job.id]);
        logger.info('thumbnail:migrate:ok', { jobId: job.id });
        succeeded++;
      } catch (err) {
        logger.warn('thumbnail:migrate:failed', { jobId: job.id, url: job.poster_url, error: err.message });
        failed++;
      }

      // Kurze Pause zwischen Downloads (externe Server schonen)
      await new Promise((r) => setTimeout(r, 300));
    }

    logger.info('thumbnail:migrate:done', { succeeded, failed, total: jobs.length });
  } catch (err) {
    logger.error('thumbnail:migrate:error', { error: err.message });
  }
}

module.exports = {
  cacheJobThumbnail,
  cacheJobThumbnailDetailed,
  promoteJobThumbnail,
  copyThumbnail,
  storeLocalThumbnail,
  deleteThumbnail,
  getThumbnailsDir,
  migrateExistingThumbnails,
  isLocalUrl,
  resolveLocalThumbnailPath,
  localThumbnailUrlExists
};
