import { NodeHttpHandler } from '@smithy/node-http-handler';
import { S3Client, GetObjectCommand, DeleteObjectCommand, HeadBucketCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Upload } from '@aws-sdk/lib-storage';
import pool from '../db/pool.js';

// ── Mutable config ────────────────────────────────────────────────────────────
let _cfg = {
  endpoint:  process.env.S3_ENDPOINT   || '',
  bucket:    process.env.S3_BUCKET     || 'mam',
  accessKey: process.env.S3_ACCESS_KEY || '',
  secretKey: process.env.S3_SECRET_KEY || '',
  region:    process.env.S3_REGION     || 'us-east-1',
};

// ── Client factory ────────────────────────────────────────────────────────────
function buildClient(cfg) {
  return new S3Client({
    region:   cfg.region,
    endpoint: cfg.endpoint || undefined,
    credentials: {
      accessKeyId:     cfg.accessKey,
      secretAccessKey: cfg.secretKey,
    },
    forcePathStyle: true,
    // Hard request/connection timeouts so a stalled RustFS GET can't hang the
    // /video and /hls endpoints forever (the original browser-playback hang).
    requestHandler: new NodeHttpHandler({ requestTimeout: 30_000, connectionTimeout: 10_000 }),
    requestChecksumCalculation: 'WHEN_REQUIRED',
    responseChecksumValidation: 'WHEN_REQUIRED',
  });
}

let _client = buildClient(_cfg);

// ── Proxy export — always delegates to the live _client ───────────────────────
// Existing code that does `s3Client.send(cmd)` continues to work even after
// rebuildS3Client() replaces the underlying instance.
export const s3Client = new Proxy(
  {},
  {
    get(_target, prop) {
      // Bug #61: bind method calls to the current _client so `this` is correct
      // even after rebuildS3Client() replaces the underlying instance.
      const val = Reflect.get(_client, prop, _client);
      return typeof val === 'function' ? val.bind(_client) : val;
    },
  }
);

// ── Bucket getter — always returns the current bucket name ───────────────────
export function getS3Bucket() { return _cfg.bucket; }

// @deprecated — import binding captures the value at module load time and
// will NOT reflect updates after rebuildS3Client(). Use getS3Bucket() instead.
export let S3_BUCKET = _cfg.bucket;

// ── Rebuild — swap in new config at runtime (called by settings API) ─────────
export function rebuildS3Client(overrides = {}) {
  _cfg      = { ..._cfg, ...overrides };
  S3_BUCKET = _cfg.bucket;
  _client   = buildClient(_cfg);
  console.log('[s3] client rebuilt — endpoint:', _cfg.endpoint || '(AWS)', '  bucket:', _cfg.bucket);
}

// ── Load config from DB on startup ───────────────────────────────────────────
// Called in index.js after migrations. DB values override env-var defaults.
export async function loadS3ConfigFromDb() {
  try {
    const result = await pool.query(
      `SELECT key, value FROM settings
       WHERE key IN ('s3_endpoint','s3_bucket','s3_access_key','s3_secret_key','s3_region')
         AND value IS NOT NULL AND value <> ''`
    );
    if (result.rows.length === 0) return;
    const overrides = {};
    for (const { key, value } of result.rows) {
      switch (key) {
        case 's3_endpoint':   overrides.endpoint  = value; break;
        case 's3_bucket':     overrides.bucket    = value; break;
        case 's3_access_key': overrides.accessKey = value; break;
        case 's3_secret_key': overrides.secretKey = value; break;
        case 's3_region':     overrides.region    = value; break;
      }
    }
    rebuildS3Client(overrides);
    console.log('[s3] config loaded from DB settings');
  } catch (err) {
    console.error('[s3] failed to load config from DB:', err.message);
  }
}

// ── One-shot test client (does not replace _client) ──────────────────────────
export function buildTestClient(cfg) {
  return buildClient({
    endpoint:  cfg.endpoint  ?? _cfg.endpoint,
    bucket:    cfg.bucket    ?? _cfg.bucket,
    accessKey: cfg.accessKey ?? _cfg.accessKey,
    secretKey: cfg.secretKey ?? _cfg.secretKey,
    region:    cfg.region    ?? _cfg.region,
  });
}

// ── Connectivity test (used by /settings/s3/test) ────────────────────────────
export async function testS3Connection(client, bucket) {
  try {
    await client.send(new HeadBucketCommand({ Bucket: bucket }));
    return { ok: true, message: `Bucket "${bucket}" is accessible` };
  } catch (headErr) {
    // Some S3-compatible stores don't implement HeadBucket — try a list
    try {
      await client.send(new ListObjectsV2Command({ Bucket: bucket, MaxKeys: 0 }));
      return { ok: true, message: `Bucket "${bucket}" is accessible` };
    } catch (listErr) {
      throw new Error(listErr.message || headErr.message);
    }
  }
}

// ── Utility helpers (all reference the live _client / _cfg) ──────────────────
export const getSignedUrlForObject = async (key, expiresIn = 3600, contentType = null) => {
  const params = { Bucket: _cfg.bucket, Key: key };
  if (contentType) params.ResponseContentType = contentType;
  return getSignedUrl(_client, new GetObjectCommand(params), { expiresIn });
};

export const uploadStream = async (key, stream, contentType) => {
  const upload = new Upload({
    client: _client,
    params: { Bucket: _cfg.bucket, Key: key, Body: stream, ContentType: contentType },
  });
  return upload.done();
};

export const deleteObject = async (key) => {
  return _client.send(new DeleteObjectCommand({ Bucket: _cfg.bucket, Key: key }));
};
