import { createHash } from 'crypto';
import { fail, warn, info } from '@zen/core/shared/logger';
import {
  signRequest,
  buildPresignedUrl,
  toBuffer,
  xmlFirst,
  xmlAll,
  sanitizeHeaderValue,
  escapeXml,
  metaToHeaders,
  headersToMeta,
  encodePath,
} from './signing.js';

// ─── Provider selection ───────────────────────────────────────────────────────

async function getConfig() {
  const provider = process.env.ZEN_STORAGE_PROVIDER ?? 'r2';
  const { getConfig: providerGetConfig } = provider === 'backblaze'
    ? await import('./backblaze.js')
    : await import('./cloudflare-r2.js');
  return providerGetConfig();
}

// ─── Storage functions ────────────────────────────────────────────────────────

async function uploadFile({ key, body, contentType, metadata = {}, cacheControl }) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}/${key}`;
    const date = new Date();
    const bodyBuffer = await toBuffer(body);

    const extraHeaders = {
      'content-type': sanitizeHeaderValue(contentType),
      ...metaToHeaders(metadata),
      ...(cacheControl && { 'cache-control': sanitizeHeaderValue(cacheControl) }),
    };

    const { url, headers } = signRequest({ method: 'PUT', host: config.host, path, extraHeaders, bodyBuffer, config, date });
    const response = await fetch(url, { method: 'PUT', headers, body: bodyBuffer });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Upload failed (${response.status}): ${text}`);
    }

    return { success: true, data: { key, bucket: config.bucket, contentType }, error: null };
  } catch (error) {
    fail(`Storage upload failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function uploadImage({ key, body, contentType, metadata = {} }) {
  return uploadFile({ key, body, contentType, metadata, cacheControl: 'public, max-age=31536000' });
}

async function deleteFile(key) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}/${key}`;
    const date = new Date();

    const { url, headers } = signRequest({ method: 'DELETE', host: config.host, path, config, date });
    const response = await fetch(url, { method: 'DELETE', headers });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Delete failed (${response.status}): ${text}`);
    }

    return { success: true, data: { key }, error: null };
  } catch (error) {
    fail(`Storage delete failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function deleteFiles(keys) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}`;
    const date = new Date();

    const xmlBody =
      `<?xml version="1.0" encoding="UTF-8"?><Delete>` +
      keys.map(k => `<Object><Key>${escapeXml(k)}</Key></Object>`).join('') +
      `</Delete>`;
    const bodyBuffer = Buffer.from(xmlBody, 'utf8');
    const contentMd5 = createHash('md5').update(bodyBuffer).digest('base64');

    const { url, headers } = signRequest({
      method: 'POST',
      host: config.host,
      path,
      query: { delete: '' },
      extraHeaders: { 'content-type': 'application/xml', 'content-md5': contentMd5 },
      bodyBuffer,
      config,
      date,
    });

    const response = await fetch(url, { method: 'POST', headers, body: bodyBuffer });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Delete failed (${response.status}): ${text}`);
    }

    const xml = await response.text();
    const deleted = xmlAll(xml, 'Deleted').map(b => ({ Key: xmlFirst(b, 'Key') }));
    const errors  = xmlAll(xml, 'Error').map(b => ({
      Key:     xmlFirst(b, 'Key'),
      Code:    xmlFirst(b, 'Code'),
      Message: xmlFirst(b, 'Message'),
    }));

    return { success: true, data: { deleted, errors }, error: null };
  } catch (error) {
    fail(`Storage batch delete failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function getFile(key) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}/${key}`;
    const date = new Date();

    const { url, headers } = signRequest({ method: 'GET', host: config.host, path, config, date });
    const response = await fetch(url, { method: 'GET', headers });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Get failed (${response.status}): ${text}`);
    }

    const buffer = Buffer.from(await response.arrayBuffer());

    return {
      success: true,
      data: {
        key,
        body: buffer,
        contentType:   response.headers.get('content-type'),
        contentLength: Number(response.headers.get('content-length')),
        lastModified:  response.headers.get('last-modified')
          ? new Date(response.headers.get('last-modified'))
          : null,
        metadata: headersToMeta(response.headers),
      },
      error: null,
    };
  } catch (error) {
    fail(`Storage get file failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function getFileMetadata(key) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}/${key}`;
    const date = new Date();

    const { url, headers } = signRequest({ method: 'HEAD', host: config.host, path, config, date });
    const response = await fetch(url, { method: 'HEAD', headers });

    if (!response.ok) {
      throw new Error(`Head failed (${response.status})`);
    }

    return {
      success: true,
      data: {
        key,
        contentType:   response.headers.get('content-type'),
        contentLength: Number(response.headers.get('content-length')),
        lastModified:  response.headers.get('last-modified')
          ? new Date(response.headers.get('last-modified'))
          : null,
        metadata: headersToMeta(response.headers),
        etag:     response.headers.get('etag'),
      },
      error: null,
    };
  } catch (error) {
    fail(`Storage get metadata failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function fileExists(key) {
  const result = await getFileMetadata(key);
  return result.success;
}

async function listFiles({ prefix = '', maxKeys = 1000, continuationToken } = {}) {
  try {
    const config = await getConfig();
    const path = `/${config.bucket}`;
    const date = new Date();

    // R2/S3 max is 1000 keys per list request
    const validMaxKeys = Math.min(Math.max(Math.floor(Number(maxKeys)), 1), 1000);

    const query = {
      'list-type': '2',
      'max-keys':  String(validMaxKeys),
      ...(prefix             && { prefix }),
      ...(continuationToken  && { 'continuation-token': continuationToken }),
    };

    const { url, headers } = signRequest({ method: 'GET', host: config.host, path, query, config, date });
    const response = await fetch(url, { method: 'GET', headers });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`List failed (${response.status}): ${text}`);
    }

    const xml = await response.text();
    const isTruncated           = xmlFirst(xml, 'IsTruncated') === 'true';
    const nextContinuationToken = xmlFirst(xml, 'NextContinuationToken');
    const files = xmlAll(xml, 'Contents').map(block => ({
      key:          xmlFirst(block, 'Key'),
      size:         parseInt(xmlFirst(block, 'Size') || '0', 10),
      lastModified: xmlFirst(block, 'LastModified') ? new Date(xmlFirst(block, 'LastModified')) : null,
      etag:         (xmlFirst(block, 'ETag') || '').replace(/"/g, ''),
    }));

    return { success: true, data: { files, isTruncated, nextContinuationToken, count: files.length }, error: null };
  } catch (error) {
    fail(`Storage list files failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function getPresignedUrl({ key, expiresIn = 3600, operation = 'get' }) {
  try {
    const config = await getConfig();
    const path   = `/${config.bucket}/${key}`;
    const date   = new Date();
    const method = operation === 'put' ? 'PUT' : 'GET';

    // R2/S3 max presigned URL lifetime is 7 days (604800 seconds)
    const validExpiresIn = Math.min(Math.max(Math.floor(Number(expiresIn)), 1), 604800);
    const url = buildPresignedUrl({ method, host: config.host, path, expiresIn: validExpiresIn, config, date });

    return { success: true, data: { key, url, expiresIn: validExpiresIn, operation }, error: null };
  } catch (error) {
    fail(`Storage presigned URL failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function copyFile({ sourceKey, destinationKey }) {
  try {
    const config = await getConfig();
    const path   = `/${config.bucket}/${destinationKey}`;
    const date   = new Date();

    const { url, headers } = signRequest({
      method: 'PUT',
      host: config.host,
      path,
      extraHeaders: { 'x-amz-copy-source': `/${config.bucket}/${encodePath(sourceKey)}` },
      config,
      date,
    });

    const response = await fetch(url, { method: 'PUT', headers });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Copy failed (${response.status}): ${text}`);
    }

    info(`Storage: copied ${sourceKey} → ${destinationKey}`);
    return { success: true, data: { key: destinationKey, bucket: config.bucket }, error: null };
  } catch (error) {
    fail(`Storage copy failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

async function proxyFile(key, { filename } = {}) {
  const result = await getFile(key);
  if (!result.success) return { success: false, error: result.error };
  return {
    success: true,
    file: {
      body:          result.data.body,
      contentType:   result.data.contentType,
      contentLength: result.data.contentLength,
      ...(filename && { filename }),
    },
  };
}

async function moveFile({ sourceKey, destinationKey }) {
  try {
    const copyResult = await copyFile({ sourceKey, destinationKey });
    if (!copyResult.success) return copyResult;

    const deleteResult = await deleteFile(sourceKey);
    if (!deleteResult.success) {
      warn(`Storage: copied to ${destinationKey} but failed to delete source ${sourceKey}`);
    } else {
      info(`Storage: moved ${sourceKey} → ${destinationKey}`);
    }

    return copyResult;
  } catch (error) {
    fail(`Storage move failed: ${error.message}`);
    return { success: false, data: null, error: error.message };
  }
}

// ─── Exports ──────────────────────────────────────────────────────────────────

export {
  generateUniqueFilename,
  getFileExtension,
  getMimeType,
  validateFileType,
  validateFileSize,
  formatFileSize,
  sanitizeFilename,
  validateUpload,
  FILE_TYPE_PRESETS,
  FILE_SIZE_LIMITS,
} from './utils.js';

export {
  uploadFile,
  uploadImage,
  deleteFile,
  deleteFiles,
  getFile,
  getFileMetadata,
  fileExists,
  listFiles,
  getPresignedUrl,
  proxyFile,
  copyFile,
  moveFile,
};

export { registerStoragePolicies, registerStoragePublicPrefixes, clearStorageConfig } from './storage-config.js';
