import { HttpError, type RequestContext } from '../../core/http.js';
import { intParam, optionalUserIdFrom, stringParam, tenantIdFrom, userIdFrom } from '../../core/request.js';
import { query, queryOne } from '../../core/db.js';
import { signStorageDownload, type StorageProviderName } from '../storage/service.js';
import {
  assertAssetSecurityScanPassed,
} from '../tenant-content/assets.js';
import {
  assetAccessTtl,
  assertCdnAccessAllowed,
  buildAssetWatermarkContext,
  recordAssetAccessEvent,
  signedAssetFingerprint,
} from '../storage/asset-access.js';

interface CatalogAssetRow {
  id: string;
  assetType: string;
  storageProvider: string;
  bucket: string | null;
  objectKey: string | null;
  title: string | null;
  fileName: string | null;
  cdnUrl: string | null;
  previewUrl: string | null;
  previewObjectKey: string | null;
  mimeType: string | null;
  visibility: string;
  regionId: string | null;
  subjectId: string | null;
  uploadStatus: string;
  securityScanStatus: string;
  previewStatus: string;
  accessRules: Record<string, unknown>;
  metadata: Record<string, unknown>;
}

async function hasActiveMembership(tenantId: string, userId: string) {
  const row = await queryOne<{ id: string }>(
    `
    select id
    from public.tenant_memberships
    where tenant_id = $1 and user_id = $2 and status = 'active'
    limit 1
    `,
    [tenantId, userId],
  );
  return !!row;
}

async function hasSvipAccess(tenantId: string, userId: string, asset: CatalogAssetRow) {
  const now = new Date().toISOString();
  const row = await queryOne<{ id: string }>(
    `
    select id
    from public.entitlements
    where tenant_id = $1
      and user_id = $2
      and entitlement_type = 'svip'
      and status = 'active'
      and starts_at <= $3::timestamptz
      and (expires_at is null or expires_at > $3::timestamptz)
      and (
        scope_type = 'tenant'
        or ($4::uuid is not null and scope_type = 'region' and scope_id = $4::uuid)
        or ($5::uuid is not null and scope_type = 'subject' and scope_id = $5::uuid)
      )
    limit 1
    `,
    [tenantId, userId, now, asset.regionId, asset.subjectId],
  );
  return !!row;
}

async function assertAssetAccess(ctx: RequestContext, asset: CatalogAssetRow) {
  if (asset.visibility === 'public' || asset.visibility === 'tenant') {
    return { userId: await optionalUserIdFrom(ctx), svip: false };
  }
  if (asset.visibility === 'private') {
    throw new HttpError(403, 'Asset is private', 'ASSET_PRIVATE');
  }

  const tenantId = await tenantIdFrom(ctx);
  const userId = await userIdFrom(ctx);
  const member = await hasActiveMembership(tenantId, userId);
  if (!member) {
    throw new HttpError(403, 'Tenant membership is required for this asset', 'ASSET_MEMBERSHIP_REQUIRED');
  }

  if (asset.visibility === 'members') return { userId, svip: false };

  const svip = await hasSvipAccess(tenantId, userId, asset);
  if (!svip) {
    throw new HttpError(403, 'SVIP entitlement is required for this asset', 'ASSET_SVIP_REQUIRED');
  }
  return { userId, svip: true };
}

async function recordDeniedAssetAccess(ctx: RequestContext, tenantId: string, asset: CatalogAssetRow, accessType: 'download' | 'preview', error: unknown) {
  const userId = await optionalUserIdFrom(ctx).catch(() => '');
  const code = error instanceof HttpError ? error.code : 'ASSET_ACCESS_DENIED';
  await recordAssetAccessEvent({
    ctx,
    tenantId,
    assetId: asset.id,
    userId: userId || null,
    actorRole: userId ? 'student' : 'anonymous',
    accessType,
    visibility: asset.visibility,
    assetType: asset.assetType,
    storageProvider: asset.storageProvider,
    result: 'denied',
    denyCode: code,
    metadata: {
      title: asset.title,
      fileName: asset.fileName,
    },
  });
}

function assetPreviewable(asset: CatalogAssetRow) {
  const mimeType = asset.mimeType?.split(';')[0]?.trim().toLowerCase() || '';
  return asset.assetType === 'pdf' || mimeType === 'application/pdf' || asset.assetType === 'image' || mimeType.startsWith('image/');
}

function assertPublishedAsset(asset: CatalogAssetRow) {
  if (asset.objectKey && asset.uploadStatus !== 'verified') {
    throw new HttpError(409, 'Asset upload has not been verified', 'ASSET_UPLOAD_NOT_VERIFIED');
  }
  assertAssetSecurityScanPassed({
    provider: asset.storageProvider as StorageProviderName,
    objectKey: asset.objectKey,
    securityScanStatus: asset.securityScanStatus,
  });
}

export async function assetsRoute(ctx: RequestContext) {
  const tenantId = await tenantIdFrom(ctx);
  const limit = intParam(ctx, 'limit', 100, 500);
  const assetType = stringParam(ctx, 'assetType');
  const regionId = stringParam(ctx, 'regionId');
  const subjectId = stringParam(ctx, 'subjectId');
  const categoryId = stringParam(ctx, 'categoryId');
  const entryId = stringParam(ctx, 'entryId');
  const contentNodeId = stringParam(ctx, 'contentNodeId');
  const includeLocked = stringParam(ctx, 'includeLocked') === 'true';
  const userPresent = Boolean(await optionalUserIdFrom(ctx));

  const params: unknown[] = [tenantId];
  const filters = [`tenant_id = $1`, `status = 'active'`, `visibility <> 'private'`];
  if (!includeLocked || !userPresent) {
    filters.push(`visibility in ('public', 'tenant')`);
  }
  if (assetType) {
    params.push(assetType);
    filters.push(`asset_type = $${params.length}`);
  }
  if (regionId) {
    params.push(regionId);
    filters.push(`(region_id = $${params.length} or region_id is null)`);
  }
  if (subjectId) {
    params.push(subjectId);
    filters.push(`(subject_id = $${params.length} or subject_id is null)`);
  }
  if (categoryId) {
    params.push(categoryId);
    filters.push(`(category_id = $${params.length} or category_id is null)`);
  }
  if (entryId) {
    params.push(entryId);
    filters.push(`(entry_id = $${params.length} or entry_id is null)`);
  }
  if (contentNodeId) {
    params.push(contentNodeId);
    filters.push(`(content_node_id = $${params.length} or content_node_id is null)`);
  }
  params.push(limit);

  const items = await query(
    `
    select id, asset_key as "assetKey", asset_type as "assetType",
           title, category as "categoryLabel", description,
           file_name as "fileName", preview_url as "previewUrl",
           mime_type as "mimeType", file_size_bytes as "fileSizeBytes",
           upload_status as "uploadStatus", preview_status as "previewStatus",
           security_scan_status as "securityScanStatus",
           visibility, region_id as "regionId", subject_id as "subjectId",
           category_id as "categoryId", node_id as "nodeId",
           entry_id as "entryId", content_node_id as "contentNodeId",
           sort_order as "order", metadata, created_at as "createdAt",
           updated_at as "updatedAt"
    from public.content_assets
    where ${filters.join(' and ')}
    order by sort_order asc, created_at desc
    limit $${params.length}
    `,
    params,
  );

  return { items };
}

export async function assetDownloadRoute(ctx: RequestContext) {
  const tenantId = await tenantIdFrom(ctx);
  const assetId = stringParam(ctx, 'assetId') || ctx.url.searchParams.get('id') || '';
  if (!assetId) {
    throw new HttpError(400, 'assetId is required', 'REQUIRED_FIELD');
  }

  const asset = await queryOne<CatalogAssetRow>(
    `
    select id, asset_type as "assetType", storage_provider as "storageProvider",
           bucket, object_key as "objectKey", title, file_name as "fileName",
           cdn_url as "cdnUrl", preview_url as "previewUrl",
           preview_object_key as "previewObjectKey", mime_type as "mimeType",
           visibility, region_id as "regionId", subject_id as "subjectId",
           upload_status as "uploadStatus",
           security_scan_status as "securityScanStatus",
           preview_status as "previewStatus",
           access_rules as "accessRules", metadata
    from public.content_assets
    where tenant_id = $1 and id = $2 and status = 'active'
    limit 1
    `,
    [tenantId, assetId],
  );

  if (!asset) {
    throw new HttpError(404, 'Asset not found', 'ASSET_NOT_FOUND');
  }

  let access: { userId: string; svip: boolean };
  try {
    access = await assertAssetAccess(ctx, asset);
    assertPublishedAsset(asset);
    assertCdnAccessAllowed({
      assetId: asset.id,
      visibility: asset.visibility,
      assetType: asset.assetType,
      storageProvider: asset.storageProvider as StorageProviderName,
      objectKey: asset.objectKey,
      cdnUrl: asset.cdnUrl,
      metadata: asset.metadata,
      accessRules: asset.accessRules,
    });
  } catch (error) {
    await recordDeniedAssetAccess(ctx, tenantId, asset, 'download', error);
    throw error;
  }

  const expiresInSec = assetAccessTtl({
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'download',
    visibility: asset.visibility,
    assetType: asset.assetType,
    disposition: 'attachment',
    requestedExpiresInSec: 900,
  });
  const download = await signStorageDownload({
    tenantId,
    provider: asset.storageProvider as StorageProviderName,
    bucket: asset.bucket,
    objectKey: asset.objectKey,
    cdnUrl: asset.cdnUrl,
    fileName: asset.fileName,
    expiresInSec,
    disposition: 'attachment',
  });
  const watermark = buildAssetWatermarkContext({
    tenantId,
    assetId: asset.id,
    userId: access.userId || null,
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'download',
    visibility: asset.visibility,
    assetType: asset.assetType,
    fileName: asset.fileName,
    title: asset.title,
    expiresAt: download.expiresAt,
    metadata: asset.metadata,
  });
  await query(
    'update public.content_assets set download_count = download_count + 1, updated_at = now() where tenant_id = $1 and id = $2',
    [tenantId, assetId],
  );
  await recordAssetAccessEvent({
    ctx,
    tenantId,
    assetId: asset.id,
    userId: access.userId || null,
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'download',
    visibility: asset.visibility,
    assetType: asset.assetType,
    storageProvider: asset.storageProvider,
    disposition: 'attachment',
    expiresInSec: download.expiresInSec,
    signatureMode: download.signatureMode,
    result: 'granted',
    metadata: { signature: signedAssetFingerprint(download), watermark },
  });

  return {
    item: {
      id: asset.id,
      assetType: asset.assetType,
      title: asset.title,
      fileName: asset.fileName,
      previewUrl: asset.previewUrl,
      uploadStatus: asset.uploadStatus,
      securityScanStatus: asset.securityScanStatus,
      previewStatus: asset.previewStatus,
      visibility: asset.visibility,
    },
    access,
    download,
    watermark,
  };
}

export async function assetPreviewRoute(ctx: RequestContext) {
  const tenantId = await tenantIdFrom(ctx);
  const assetId = stringParam(ctx, 'assetId') || ctx.url.searchParams.get('id') || '';
  if (!assetId) {
    throw new HttpError(400, 'assetId is required', 'REQUIRED_FIELD');
  }

  const asset = await queryOne<CatalogAssetRow>(
    `
    select id, asset_type as "assetType", storage_provider as "storageProvider",
           bucket, object_key as "objectKey", title, file_name as "fileName",
           cdn_url as "cdnUrl", preview_url as "previewUrl",
           preview_object_key as "previewObjectKey", mime_type as "mimeType",
           visibility, region_id as "regionId", subject_id as "subjectId",
           upload_status as "uploadStatus",
           security_scan_status as "securityScanStatus",
           preview_status as "previewStatus",
           access_rules as "accessRules", metadata
    from public.content_assets
    where tenant_id = $1 and id = $2 and status = 'active'
    limit 1
    `,
    [tenantId, assetId],
  );

  if (!asset) {
    throw new HttpError(404, 'Asset not found', 'ASSET_NOT_FOUND');
  }
  if (!assetPreviewable(asset)) {
    throw new HttpError(400, 'Asset type does not support inline preview', 'ASSET_PREVIEW_NOT_SUPPORTED');
  }

  let access: { userId: string; svip: boolean };
  try {
    access = await assertAssetAccess(ctx, asset);
    assertPublishedAsset(asset);
    assertCdnAccessAllowed({
      assetId: asset.id,
      visibility: asset.visibility,
      assetType: asset.assetType,
      storageProvider: asset.storageProvider as StorageProviderName,
      objectKey: asset.previewObjectKey || asset.objectKey,
      cdnUrl: asset.previewUrl || asset.cdnUrl,
      metadata: asset.metadata,
      accessRules: asset.accessRules,
    });
  } catch (error) {
    await recordDeniedAssetAccess(ctx, tenantId, asset, 'preview', error);
    throw error;
  }
  const expiresInSec = assetAccessTtl({
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'preview',
    visibility: asset.visibility,
    assetType: asset.assetType,
    disposition: 'inline',
    requestedExpiresInSec: 900,
  });
  const preview = await signStorageDownload({
    tenantId,
    provider: asset.storageProvider as StorageProviderName,
    bucket: asset.bucket,
    objectKey: asset.previewObjectKey || asset.objectKey,
    cdnUrl: asset.previewUrl || asset.cdnUrl,
    fileName: asset.fileName || asset.title || 'preview.pdf',
    expiresInSec,
    disposition: 'inline',
  });
  const watermark = buildAssetWatermarkContext({
    tenantId,
    assetId: asset.id,
    userId: access.userId || null,
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'preview',
    visibility: asset.visibility,
    assetType: asset.assetType,
    fileName: asset.fileName,
    title: asset.title,
    expiresAt: preview.expiresAt,
    metadata: asset.metadata,
  });
  await recordAssetAccessEvent({
    ctx,
    tenantId,
    assetId: asset.id,
    userId: access.userId || null,
    actorRole: access.userId ? 'student' : 'anonymous',
    accessType: 'preview',
    visibility: asset.visibility,
    assetType: asset.assetType,
    storageProvider: asset.storageProvider,
    disposition: 'inline',
    expiresInSec: preview.expiresInSec,
    signatureMode: preview.signatureMode,
    result: 'granted',
    metadata: { signature: signedAssetFingerprint(preview), watermark },
  });

  return {
    item: {
      id: asset.id,
      assetType: asset.assetType,
      title: asset.title,
      fileName: asset.fileName,
      previewUrl: asset.previewUrl,
      previewStatus: asset.previewStatus,
      securityScanStatus: asset.securityScanStatus,
      visibility: asset.visibility,
    },
    access,
    preview,
    watermark,
  };
}
