import crypto from 'node:crypto';
import OSS from 'ali-oss';
import { StorageClient } from '@supabase/storage-js';
import { config } from '../../core/config.js';
import { HttpError } from '../../core/http.js';

export type StorageProviderName = 'external_url' | 'supabase_storage' | 'aliyun_oss' | 'tencent_cos' | 'qiniu_kodo' | 'local_dev';
export type StorageHttpMethod = 'GET' | 'PUT' | 'HEAD';
export type StorageContentDisposition = 'attachment' | 'inline';

export interface AssetLocation {
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  cdnUrl?: string | null;
}

export interface UploadSignInput {
  tenantId: string;
  provider: StorageProviderName;
  bucket: string;
  objectKey: string;
  fileName: string;
  mimeType: string;
  fileSizeBytes: number | null;
  expiresInSec: number;
  upsert?: boolean;
}

export interface DownloadSignInput {
  tenantId: string;
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  cdnUrl?: string | null;
  fileName?: string | null;
  expiresInSec: number;
  disposition?: StorageContentDisposition;
}

export interface SignedStorageUrl {
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  method: StorageHttpMethod;
  url: string;
  headers: Record<string, string>;
  expiresAt: string;
  expiresInSec: number;
  signatureMode: string;
}

export interface HeadStorageObjectInput {
  tenantId: string;
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  declaredMimeType?: string | null;
  declaredFileSizeBytes?: number | null;
  declaredChecksumSha256?: string | null;
}

export interface StorageObjectMetadata {
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  exists: boolean;
  sizeBytes: number | null;
  mimeType: string | null;
  checksumSha256: string | null;
  etag: string | null;
  lastModified: string | null;
  rawHeaders: Record<string, string>;
  verificationSource: string;
}

const REAL_STORAGE_PROVIDERS = new Set<StorageProviderName>(['aliyun_oss', 'tencent_cos', 'supabase_storage']);
const SUPPORTED_STORAGE_PROVIDERS = new Set<StorageProviderName>([
  'external_url',
  'supabase_storage',
  'aliyun_oss',
  'tencent_cos',
  'qiniu_kodo',
  'local_dev',
]);
const UPLOADABLE_PROVIDERS = new Set<StorageProviderName>(['local_dev', 'supabase_storage', 'aliyun_oss', 'tencent_cos']);
const SAFE_OBJECT_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9._~!$&'()+,;=@/-]{0,1023}$/;

function nowSeconds() {
  return Math.floor(Date.now() / 1000);
}

function expiresAt(expiresInSec: number) {
  return new Date(Date.now() + expiresInSec * 1000).toISOString();
}

function assertProvider(provider: string): asserts provider is StorageProviderName {
  if (!SUPPORTED_STORAGE_PROVIDERS.has(provider as StorageProviderName)) {
    throw new HttpError(400, `Unsupported storage provider: ${provider}`, 'UNSUPPORTED_STORAGE_PROVIDER');
  }
}

function requireConfigured(condition: unknown, provider: StorageProviderName, missing: string) {
  if (!condition) {
    throw new HttpError(500, `${provider} is not configured: ${missing}`, 'STORAGE_PROVIDER_NOT_CONFIGURED');
  }
}

function canonicalObjectKey(objectKey: string) {
  return objectKey.replace(/^\/+/, '').replace(/\/{2,}/g, '/');
}

export function configuredDefaultStorageProvider(): StorageProviderName {
  const provider = config.storageDefaultProvider.trim() || 'local_dev';
  assertProvider(provider);
  return provider;
}

export function configuredDefaultStorageBucket() {
  return config.storageDefaultBucket.trim() || 'tenant-assets';
}

export function validateObjectKey(tenantId: string, objectKey: string) {
  const clean = canonicalObjectKey(objectKey);
  if (!clean || clean.includes('..') || clean.includes('\\') || clean.includes('%2f') || clean.includes('%2F')) {
    throw new HttpError(400, 'Invalid objectKey', 'INVALID_OBJECT_KEY');
  }
  if (!SAFE_OBJECT_KEY_RE.test(clean)) {
    throw new HttpError(400, 'objectKey contains unsafe characters', 'INVALID_OBJECT_KEY');
  }
  if (config.storageRequireTenantPrefix && !clean.startsWith(`${tenantId}/`)) {
    throw new HttpError(400, 'objectKey must be scoped by tenantId prefix', 'OBJECT_KEY_TENANT_PREFIX_REQUIRED');
  }
  return clean;
}

export function validateMimeType(mimeType: string) {
  const normalized = mimeType.trim().toLowerCase();
  if (!normalized || normalized.length > 160 || normalized.includes('\r') || normalized.includes('\n')) {
    throw new HttpError(400, 'Invalid mimeType', 'INVALID_MIME_TYPE');
  }
  const exact = config.storageAllowedMimeTypes.map(item => item.toLowerCase());
  const prefixes = config.storageAllowedMimePrefixes.map(item => item.toLowerCase());
  if (!exact.includes(normalized) && !prefixes.some(prefix => normalized.startsWith(prefix))) {
    throw new HttpError(400, `mimeType is not allowed: ${normalized}`, 'MIME_TYPE_NOT_ALLOWED');
  }
  return normalized;
}

export function validateFileSize(fileSizeBytes: number | null) {
  if (fileSizeBytes === null) return null;
  if (!Number.isSafeInteger(fileSizeBytes) || fileSizeBytes < 0) {
    throw new HttpError(400, 'fileSizeBytes must be a non-negative safe integer', 'INVALID_FILE_SIZE');
  }
  if (fileSizeBytes > config.storageMaxUploadBytes) {
    throw new HttpError(400, 'file exceeds STORAGE_MAX_UPLOAD_BYTES', 'FILE_TOO_LARGE');
  }
  return fileSizeBytes;
}

export function normalizeStorageProvider(value: string | null | undefined, fallback?: StorageProviderName): StorageProviderName {
  const provider = (value?.trim() || fallback || configuredDefaultStorageProvider()) as StorageProviderName;
  assertProvider(provider);
  return provider;
}

export function assertUploadProvider(provider: StorageProviderName) {
  if (!UPLOADABLE_PROVIDERS.has(provider)) {
    throw new HttpError(400, `${provider} does not support managed upload signing`, 'UPLOAD_PROVIDER_NOT_SUPPORTED');
  }
}

export function assertWritableLocation(location: AssetLocation) {
  if (REAL_STORAGE_PROVIDERS.has(location.provider) && (!location.bucket || !location.objectKey)) {
    throw new HttpError(400, `${location.provider} asset requires bucket and objectKey`, 'ASSET_OBJECT_LOCATION_REQUIRED');
  }
}

function localSignedUrl(input: {
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  method: StorageHttpMethod;
  expiresInSec: number;
  disposition?: StorageContentDisposition;
  headers?: Record<string, string>;
}): SignedStorageUrl {
  const expires = expiresAt(input.expiresInSec);
  const base = config.storagePublicBaseUrl.replace(/\/+$/, '');
  const path = `${encodeURIComponent(input.bucket || 'default')}/${encodeURI(input.objectKey || 'missing')}`;
  const url = base
    ? `${base}/${path}?expiresAt=${encodeURIComponent(expires)}&disposition=${input.disposition || 'attachment'}`
    : `${input.provider}://${input.bucket || 'default'}/${input.objectKey || 'missing'}?expiresAt=${encodeURIComponent(expires)}&disposition=${input.disposition || 'attachment'}`;

  return {
    provider: input.provider,
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: input.method,
    url,
    headers: input.headers || {},
    expiresAt: expires,
    expiresInSec: input.expiresInSec,
    signatureMode: 'local-placeholder',
  };
}

async function signAliyunOss(input: {
  bucket: string;
  objectKey: string;
  method: StorageHttpMethod;
  mimeType?: string;
  fileName?: string | null;
  expiresInSec: number;
  disposition?: StorageContentDisposition;
}): Promise<SignedStorageUrl> {
  requireConfigured(config.aliyunOssAccessKeyId, 'aliyun_oss', 'ALIYUN_OSS_ACCESS_KEY_ID');
  requireConfigured(config.aliyunOssAccessKeySecret, 'aliyun_oss', 'ALIYUN_OSS_ACCESS_KEY_SECRET');
  requireConfigured(config.aliyunOssRegion || config.aliyunOssEndpoint, 'aliyun_oss', 'ALIYUN_OSS_REGION or ALIYUN_OSS_ENDPOINT');

  const client = new OSS({
    region: config.aliyunOssRegion || undefined,
    endpoint: config.aliyunOssEndpoint || undefined,
    accessKeyId: config.aliyunOssAccessKeyId,
    accessKeySecret: config.aliyunOssAccessKeySecret,
    stsToken: config.aliyunOssStsToken || undefined,
    bucket: input.bucket,
    internal: config.aliyunOssInternal,
    secure: true,
  });

  const headers: Record<string, string> = {};
  const options: Record<string, unknown> = {
    expires: input.expiresInSec,
    method: input.method,
  };
  if (input.method === 'PUT' && input.mimeType) {
    options['Content-Type'] = input.mimeType;
    headers['content-type'] = input.mimeType;
  }
  if (input.method === 'GET' && input.fileName) {
    options.response = {
      'content-disposition': `${input.disposition || 'attachment'}; filename="${encodeURIComponent(input.fileName)}"`,
    };
  }

  const url = client.signatureUrl(input.objectKey, options);
  return {
    provider: 'aliyun_oss',
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: input.method,
    url,
    headers,
    expiresAt: expiresAt(input.expiresInSec),
    expiresInSec: input.expiresInSec,
    signatureMode: 'aliyun-oss-signature-url-v1',
  };
}

function cosEncodePath(objectKey: string) {
  return objectKey
    .split('/')
    .map(part => encodeURIComponent(part).replace(/[!'()*]/g, char => `%${char.charCodeAt(0).toString(16).toUpperCase()}`))
    .join('/');
}

function hmacSha1Hex(key: string | Buffer, value: string) {
  return crypto.createHmac('sha1', key).update(value).digest('hex');
}

function sha1Hex(value: string) {
  return crypto.createHash('sha1').update(value).digest('hex');
}

function cosHost(bucket: string) {
  requireConfigured(config.tencentCosRegion, 'tencent_cos', 'TENCENT_COS_REGION');
  const bucketWithAppId = config.tencentCosAppId && !bucket.endsWith(`-${config.tencentCosAppId}`)
    ? `${bucket}-${config.tencentCosAppId}`
    : bucket;
  return `${bucketWithAppId}.cos.${config.tencentCosRegion}.myqcloud.com`;
}

function signTencentCos(input: {
  bucket: string;
  objectKey: string;
  method: StorageHttpMethod;
  mimeType?: string;
  expiresInSec: number;
  responseContentDisposition?: string;
}): SignedStorageUrl {
  requireConfigured(config.tencentCosSecretId, 'tencent_cos', 'TENCENT_COS_SECRET_ID');
  requireConfigured(config.tencentCosSecretKey, 'tencent_cos', 'TENCENT_COS_SECRET_KEY');
  const host = cosHost(input.bucket);
  const start = nowSeconds();
  const end = start + input.expiresInSec;
  const keyTime = `${start};${end}`;
  const method = input.method.toLowerCase();
  const pathname = `/${cosEncodePath(input.objectKey)}`;
  const signedHeaders: Record<string, string> = { host };
  if (input.method === 'PUT' && input.mimeType) signedHeaders['content-type'] = input.mimeType;
  const headerKeys = Object.keys(signedHeaders).sort();
  const headerList = headerKeys.join(';');
  const httpHeaders = headerKeys
    .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(signedHeaders[key]).toLowerCase()}`)
    .join('&');
  const signedQuery: Record<string, string> = {};
  if (config.tencentCosSecurityToken) signedQuery['x-cos-security-token'] = config.tencentCosSecurityToken;
  if (input.method === 'GET' && input.responseContentDisposition) {
    signedQuery['response-content-disposition'] = input.responseContentDisposition;
  }
  const queryKeys = Object.keys(signedQuery).sort();
  const urlParamList = queryKeys.join(';');
  const httpParameters = queryKeys
    .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(signedQuery[key])}`)
    .join('&');
  const httpString = `${method}\n${pathname}\n${httpParameters}\n${httpHeaders}\n`;
  const stringToSign = `sha1\n${keyTime}\n${sha1Hex(httpString)}\n`;
  const signKey = hmacSha1Hex(config.tencentCosSecretKey, keyTime);
  const signature = hmacSha1Hex(signKey, stringToSign);
  const query = new URLSearchParams();
  query.set('q-sign-algorithm', 'sha1');
  query.set('q-ak', config.tencentCosSecretId);
  query.set('q-sign-time', keyTime);
  query.set('q-key-time', keyTime);
  query.set('q-header-list', headerList);
  query.set('q-url-param-list', urlParamList);
  query.set('q-signature', signature);
  for (const key of queryKeys) query.set(key, signedQuery[key]);

  const headers: Record<string, string> = {};
  if (input.method === 'PUT' && input.mimeType) headers['content-type'] = input.mimeType;

  return {
    provider: 'tencent_cos',
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: input.method,
    url: `https://${host}${pathname}?${query.toString()}`,
    headers,
    expiresAt: expiresAt(input.expiresInSec),
    expiresInSec: input.expiresInSec,
    signatureMode: 'tencent-cos-signature-url-v5',
  };
}

function supabaseStorageClient() {
  requireConfigured(config.supabaseStorageUrl, 'supabase_storage', 'SUPABASE_STORAGE_URL');
  requireConfigured(config.supabaseStorageServiceKey, 'supabase_storage', 'SUPABASE_STORAGE_SERVICE_KEY');
  return new StorageClient(config.supabaseStorageUrl.replace(/\/+$/, ''), {
    apikey: config.supabaseStorageServiceKey,
    authorization: `Bearer ${config.supabaseStorageServiceKey}`,
  });
}

async function signSupabaseStorageUpload(input: UploadSignInput): Promise<SignedStorageUrl> {
  const response = await supabaseStorageClient().from(input.bucket).createSignedUploadUrl(input.objectKey, {
    upsert: input.upsert === true,
  });
  if (response.error || !response.data) {
    throw new HttpError(502, response.error?.message || 'Supabase Storage upload signing failed', 'STORAGE_SIGN_FAILED');
  }
  return {
    provider: 'supabase_storage',
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: 'PUT',
    url: response.data.signedUrl,
    headers: { 'content-type': input.mimeType },
    expiresAt: expiresAt(7200),
    expiresInSec: 7200,
    signatureMode: 'supabase-storage-signed-upload-url',
  };
}

async function signSupabaseStorageDownload(input: DownloadSignInput): Promise<SignedStorageUrl> {
  if (!input.bucket || !input.objectKey) {
    throw new HttpError(400, 'Supabase Storage asset requires bucket and objectKey', 'ASSET_OBJECT_LOCATION_REQUIRED');
  }
  const options = input.disposition === 'inline'
    ? undefined
    : { download: input.fileName || true };
  const response = await supabaseStorageClient().from(input.bucket).createSignedUrl(input.objectKey, input.expiresInSec, options);
  if (response.error || !response.data) {
    throw new HttpError(502, response.error?.message || 'Supabase Storage download signing failed', 'STORAGE_SIGN_FAILED');
  }
  return {
    provider: 'supabase_storage',
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: 'GET',
    url: response.data.signedUrl,
    headers: {},
    expiresAt: expiresAt(input.expiresInSec),
    expiresInSec: input.expiresInSec,
    signatureMode: 'supabase-storage-signed-url',
  };
}

export async function signStorageUpload(input: UploadSignInput): Promise<SignedStorageUrl> {
  assertUploadProvider(input.provider);
  validateObjectKey(input.tenantId, input.objectKey);
  const mimeType = validateMimeType(input.mimeType);
  validateFileSize(input.fileSizeBytes);

  if (input.provider === 'local_dev') {
    return localSignedUrl({
      provider: input.provider,
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'PUT',
      expiresInSec: input.expiresInSec,
      disposition: 'attachment',
      headers: { 'content-type': mimeType },
    });
  }
  if (input.provider === 'aliyun_oss') {
    return signAliyunOss({
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'PUT',
      mimeType,
      expiresInSec: input.expiresInSec,
    });
  }
  if (input.provider === 'tencent_cos') {
    return signTencentCos({
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'PUT',
      mimeType,
      expiresInSec: input.expiresInSec,
    });
  }
  return signSupabaseStorageUpload({ ...input, mimeType });
}

export async function signStorageDownload(input: DownloadSignInput): Promise<SignedStorageUrl> {
  if (input.cdnUrl) {
    return {
      provider: input.provider,
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'GET',
      url: input.cdnUrl,
      headers: {},
      expiresAt: expiresAt(input.expiresInSec),
      expiresInSec: input.expiresInSec,
      signatureMode: 'public-or-provider-managed',
    };
  }
  if (!input.objectKey) {
    throw new HttpError(400, 'Asset requires objectKey or cdnUrl', 'ASSET_LOCATION_REQUIRED');
  }
  validateObjectKey(input.tenantId, input.objectKey);

  if (input.provider === 'local_dev' || input.provider === 'qiniu_kodo') {
    return localSignedUrl({
      provider: input.provider,
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'GET',
      expiresInSec: input.expiresInSec,
      disposition: input.disposition || 'attachment',
    });
  }
  if (input.provider === 'aliyun_oss') {
    if (!input.bucket) throw new HttpError(400, 'Aliyun OSS asset requires bucket', 'ASSET_OBJECT_LOCATION_REQUIRED');
    return signAliyunOss({
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'GET',
      fileName: input.fileName,
      expiresInSec: input.expiresInSec,
      disposition: input.disposition || 'attachment',
    });
  }
  if (input.provider === 'tencent_cos') {
    if (!input.bucket) throw new HttpError(400, 'Tencent COS asset requires bucket', 'ASSET_OBJECT_LOCATION_REQUIRED');
    return signTencentCos({
      bucket: input.bucket,
      objectKey: input.objectKey,
      method: 'GET',
      responseContentDisposition: input.fileName
        ? `${input.disposition || 'attachment'}; filename="${encodeURIComponent(input.fileName)}"`
        : undefined,
      expiresInSec: input.expiresInSec,
    });
  }
  if (input.provider === 'supabase_storage') {
    return signSupabaseStorageDownload(input);
  }
  throw new HttpError(400, `${input.provider} asset requires cdnUrl`, 'ASSET_LOCATION_REQUIRED');
}

function normalizeHeaderMap(headers: Headers | Record<string, unknown>) {
  const normalized: Record<string, string> = {};
  if (headers instanceof Headers) {
    headers.forEach((value, key) => {
      normalized[key.toLowerCase()] = value;
    });
    return normalized;
  }
  for (const [key, value] of Object.entries(headers)) {
    if (value === undefined || value === null) continue;
    normalized[key.toLowerCase()] = Array.isArray(value) ? String(value[0] || '') : String(value);
  }
  return normalized;
}

function numberHeader(headers: Record<string, string>, key: string) {
  const value = Number(headers[key]);
  return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
}

function firstHeader(headers: Record<string, string>, keys: string[]) {
  for (const key of keys) {
    const value = headers[key.toLowerCase()];
    if (value) return value;
  }
  return null;
}

function cleanEtag(value: string | null) {
  return value ? value.replace(/^"+|"+$/g, '') : null;
}

function metadataFromHeaders(input: {
  provider: StorageProviderName;
  bucket: string | null;
  objectKey: string | null;
  headers: Record<string, string>;
  verificationSource: string;
}): StorageObjectMetadata {
  const checksumSha256 = firstHeader(input.headers, [
    'x-oss-meta-sha256',
    'x-oss-meta-checksum-sha256',
    'x-cos-meta-sha256',
    'x-cos-meta-checksum-sha256',
    'x-amz-meta-sha256',
    'x-amz-meta-checksum-sha256',
  ]);

  return {
    provider: input.provider,
    bucket: input.bucket,
    objectKey: input.objectKey,
    exists: true,
    sizeBytes: numberHeader(input.headers, 'content-length'),
    mimeType: firstHeader(input.headers, ['content-type']),
    checksumSha256: checksumSha256?.trim().toLowerCase() || null,
    etag: cleanEtag(firstHeader(input.headers, ['etag'])),
    lastModified: firstHeader(input.headers, ['last-modified']),
    rawHeaders: input.headers,
    verificationSource: input.verificationSource,
  };
}

async function headAliyunOssObject(input: HeadStorageObjectInput): Promise<StorageObjectMetadata> {
  if (!input.bucket || !input.objectKey) {
    throw new HttpError(400, 'Aliyun OSS asset requires bucket and objectKey', 'ASSET_OBJECT_LOCATION_REQUIRED');
  }
  requireConfigured(config.aliyunOssAccessKeyId, 'aliyun_oss', 'ALIYUN_OSS_ACCESS_KEY_ID');
  requireConfigured(config.aliyunOssAccessKeySecret, 'aliyun_oss', 'ALIYUN_OSS_ACCESS_KEY_SECRET');
  requireConfigured(config.aliyunOssRegion || config.aliyunOssEndpoint, 'aliyun_oss', 'ALIYUN_OSS_REGION or ALIYUN_OSS_ENDPOINT');

  const client = new OSS({
    region: config.aliyunOssRegion || undefined,
    endpoint: config.aliyunOssEndpoint || undefined,
    accessKeyId: config.aliyunOssAccessKeyId,
    accessKeySecret: config.aliyunOssAccessKeySecret,
    stsToken: config.aliyunOssStsToken || undefined,
    bucket: input.bucket,
    internal: config.aliyunOssInternal,
    secure: true,
  });

  try {
    const response = await client.head(input.objectKey);
    const headers = normalizeHeaderMap(response.res?.headers || response);
    return metadataFromHeaders({
      provider: 'aliyun_oss',
      bucket: input.bucket,
      objectKey: input.objectKey,
      headers,
      verificationSource: 'aliyun-oss-head-object',
    });
  } catch (error) {
    const status = Number((error as { status?: number; statusCode?: number }).status || (error as { statusCode?: number }).statusCode || 0);
    if (status === 404) {
      throw new HttpError(409, 'Uploaded object was not found in Aliyun OSS', 'STORAGE_OBJECT_NOT_FOUND');
    }
    throw error;
  }
}

async function headTencentCosObject(input: HeadStorageObjectInput): Promise<StorageObjectMetadata> {
  if (!input.bucket || !input.objectKey) {
    throw new HttpError(400, 'Tencent COS asset requires bucket and objectKey', 'ASSET_OBJECT_LOCATION_REQUIRED');
  }
  const signed = signTencentCos({
    bucket: input.bucket,
    objectKey: input.objectKey,
    method: 'HEAD',
    expiresInSec: 60,
  });
  const response = await fetch(signed.url, { method: 'HEAD' });
  if (response.status === 404) {
    throw new HttpError(409, 'Uploaded object was not found in Tencent COS', 'STORAGE_OBJECT_NOT_FOUND');
  }
  if (!response.ok) {
    throw new HttpError(502, `Tencent COS object metadata check failed: ${response.status}`, 'STORAGE_HEAD_FAILED');
  }
  return metadataFromHeaders({
    provider: 'tencent_cos',
    bucket: input.bucket,
    objectKey: input.objectKey,
    headers: normalizeHeaderMap(response.headers),
    verificationSource: 'tencent-cos-head-object',
  });
}

async function headSupabaseStorageObject(input: HeadStorageObjectInput): Promise<StorageObjectMetadata> {
  if (!input.bucket || !input.objectKey) {
    throw new HttpError(400, 'Supabase Storage asset requires bucket and objectKey', 'ASSET_OBJECT_LOCATION_REQUIRED');
  }
  const parts = input.objectKey.split('/');
  const name = parts.pop() || '';
  const folder = parts.join('/');
  const response = await supabaseStorageClient().from(input.bucket).list(folder || undefined, {
    search: name,
    limit: 20,
  });
  if (response.error) {
    throw new HttpError(502, response.error.message || 'Supabase Storage metadata check failed', 'STORAGE_HEAD_FAILED');
  }
  const file = response.data?.find(item => item.name === name);
  if (!file) {
    throw new HttpError(409, 'Uploaded object was not found in Supabase Storage', 'STORAGE_OBJECT_NOT_FOUND');
  }
  const metadata = (file as { metadata?: Record<string, unknown> }).metadata || {};
  const headers: Record<string, string> = {};
  for (const [key, value] of Object.entries(metadata)) {
    if (value !== undefined && value !== null) headers[key.toLowerCase()] = String(value);
  }
  const size = Number(metadata.size ?? metadata.contentLength);
  return {
    provider: 'supabase_storage',
    bucket: input.bucket,
    objectKey: input.objectKey,
    exists: true,
    sizeBytes: Number.isFinite(size) && size >= 0 ? Math.trunc(size) : null,
    mimeType: typeof metadata.mimetype === 'string' ? metadata.mimetype : typeof metadata.contentType === 'string' ? metadata.contentType : null,
    checksumSha256: typeof metadata.sha256 === 'string' ? metadata.sha256.trim().toLowerCase() : null,
    etag: typeof metadata.eTag === 'string' ? metadata.eTag : typeof metadata.etag === 'string' ? metadata.etag : null,
    lastModified: typeof file.updated_at === 'string' ? file.updated_at : null,
    rawHeaders: headers,
    verificationSource: 'supabase-storage-list-metadata',
  };
}

export async function headStorageObject(input: HeadStorageObjectInput): Promise<StorageObjectMetadata> {
  if (!input.objectKey) {
    throw new HttpError(400, 'Asset requires objectKey', 'ASSET_LOCATION_REQUIRED');
  }
  const objectKey = validateObjectKey(input.tenantId, input.objectKey);
  const fileSizeBytes = input.declaredFileSizeBytes === undefined
    ? null
    : validateFileSize(input.declaredFileSizeBytes);
  const mimeType = input.declaredMimeType ? validateMimeType(input.declaredMimeType) : null;
  const checksumSha256 = input.declaredChecksumSha256?.trim().toLowerCase() || null;

  if (input.provider === 'local_dev') {
    return {
      provider: input.provider,
      bucket: input.bucket,
      objectKey,
      exists: true,
      sizeBytes: fileSizeBytes,
      mimeType,
      checksumSha256,
      etag: checksumSha256,
      lastModified: new Date().toISOString(),
      rawHeaders: {},
      verificationSource: 'local-dev-declared-metadata',
    };
  }
  if (input.provider === 'aliyun_oss') {
    return headAliyunOssObject({ ...input, objectKey });
  }
  if (input.provider === 'tencent_cos') {
    return headTencentCosObject({ ...input, objectKey });
  }
  if (input.provider === 'supabase_storage') {
    return headSupabaseStorageObject({ ...input, objectKey });
  }
  throw new HttpError(400, `${input.provider} does not support upload confirmation`, 'UPLOAD_CONFIRM_PROVIDER_NOT_SUPPORTED');
}
