'use strict';

const http = require('node:http');
const https = require('node:https');
const fs = require('node:fs');
const fsp = require('node:fs/promises');
const path = require('node:path');
const { URL } = require('node:url');

const { safeName, extFromContentType } = require('./http');

const FILES_DIR = process.env.BANGUMI_VAULT_FILES_DIR || '/data/files';
const COVERS_DIR = path.join(FILES_DIR, 'covers');
const LOGS_DIR = path.join(FILES_DIR, 'logs');

function ensureDirs() {
  fs.mkdirSync(COVERS_DIR, { recursive: true });
  fs.mkdirSync(LOGS_DIR, { recursive: true });
}

function downloadBuffer(remoteUrl) {
  return new Promise((resolve, reject) => {
    let parsed;
    try {
      parsed = new URL(remoteUrl);
    } catch {
      reject(new Error('invalid url'));
      return;
    }
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      reject(new Error('unsupported url protocol'));
      return;
    }
    const client = parsed.protocol === 'https:' ? https : http;
    const req = client.get(parsed, {
      headers: { 'User-Agent': 'BangumiVaultDocker/1.0', 'Referer': 'https://bgm.tv/' },
      timeout: 30000
    }, res => {
      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
        res.resume();
        downloadBuffer(new URL(res.headers.location, parsed).toString()).then(resolve, reject);
        return;
      }
      if (res.statusCode < 200 || res.statusCode >= 300) {
        res.resume();
        reject(new Error(`HTTP ${res.statusCode}`));
        return;
      }
      const chunks = [];
      let size = 0;
      res.on('data', chunk => {
        size += chunk.length;
        if (size > 25 * 1024 * 1024) {
          req.destroy(new Error('image too large'));
          return;
        }
        chunks.push(chunk);
      });
      res.on('end', () => resolve({ buffer: Buffer.concat(chunks), contentType: res.headers['content-type'] || '' }));
    });
    req.on('timeout', () => req.destroy(new Error('download timeout')));
    req.on('error', reject);
  });
}

// Rewrite an online image URL through the configured proxy/mirror (empty = no change).
function applyImageProxy(url, proxy) {
  proxy = (proxy || '').trim();
  if (!proxy || !url || !/^https?:\/\//i.test(url)) return url;
  return proxy.includes('{url}') ? proxy.replace('{url}', encodeURIComponent(url)) : proxy + url;
}

// Download one cover and write it to disk. Returns { url, file } paths used by the app.
async function cacheCover(subjectId, remoteUrl, imageProxy) {
  const sid = safeName(subjectId, 'subject');
  const downloaded = await downloadBuffer(applyImageProxy(remoteUrl, imageProxy));
  const file = `${sid}${extFromContentType(downloaded.contentType, remoteUrl)}`;
  await fsp.writeFile(path.join(COVERS_DIR, file), downloaded.buffer);
  return { url: `/images/${file}`, file: `covers/${file}` };
}

function onlineCoverUrl(c) {
  const imgs = c.images || {};
  return c.image || imgs.large || imgs.common || imgs.medium || imgs.grid || imgs.small || '';
}

// Cache covers for every collection that doesn't already have a local copy.
// Mutates each row in-place (cover_local_url / cover_local_file / cover_cached_at).
async function cacheCoversForState(collections, onProgress, imageProxy) {
  const rows = Object.values(collections || {}).filter(c => c.status !== 'confirmed_deleted' && !c.cover_local_url);
  let ok = 0, fail = 0;
  for (let i = 0; i < rows.length; i++) {
    const c = rows[i];
    const url = onlineCoverUrl(c);
    if (!url) { fail++; continue; }
    try {
      const r = await cacheCover(c.subject_id, url, imageProxy);
      c.cover_local_url = r.url;
      c.cover_local_file = r.file;
      c.cover_cached_at = new Date().toISOString();
      ok++;
    } catch {
      fail++;
    }
    if (onProgress) onProgress(i + 1, rows.length, ok, fail);
    await new Promise(r => setTimeout(r, 35));
  }
  return { ok, fail, total: rows.length };
}

module.exports = { FILES_DIR, COVERS_DIR, LOGS_DIR, ensureDirs, downloadBuffer, cacheCover, cacheCoversForState };
