import { Hono } from 'hono';
import type { Env } from '../types.js';
import { CONTENT_TYPES, parsePagination } from '../types.js';
import type { ContentType } from '../types.js';
import { requireApiKeyScope, requireAuth } from '../middleware/auth.js';
import type { AuthContext } from '../middleware/auth.js';
import { createAdminClient } from '../db/client.js';
import type { Database } from '../db/types.js';
import { validateEssence } from '@decantr/essence-spec';
import { validateRegistryContent } from '../lib/content-validation.js';
import { getContentIntelligence } from '../lib/content-intelligence.js';
import { getCommercialLimits } from '../lib/entitlements.js';
import { recordAuditEvent } from '../lib/audit-log.js';
import { recordUsageEvent } from '../lib/usage-metering.js';
import { emitApiTelemetry } from '../lib/telemetry.js';
import {
  getSignedThumbnailUrl,
  REGISTRY_THUMBNAIL_BUCKET,
} from '../lib/content-presentation.js';

export const publishRoutes = new Hono<Env>();

type ContentUpdate = Database['public']['Tables']['content']['Update'];

// All publish routes require auth
publishRoutes.use('/*', requireAuth());

function sanitizeFileName(fileName: string): string {
  return fileName
    .toLowerCase()
    .replace(/[^a-z0-9._-]+/g, '-')
    .replace(/-{2,}/g, '-')
    .replace(/^-|-$/g, '');
}

// POST /v1/content/thumbnail-upload - Create signed upload target
publishRoutes.post('/content/thumbnail-upload', requireApiKeyScope('content:write'), async (c) => {
  const auth = c.get('auth') as AuthContext;
  const body = await c.req.json();
  const target = body.target === 'organization' ? 'organization' : body.target === 'personal' ? 'personal' : 'community';
  const fileName = typeof body.file_name === 'string' ? sanitizeFileName(body.file_name) : '';
  const orgSlug = typeof body.org_slug === 'string' ? body.org_slug.trim() : '';
  const username = auth.user!.username?.trim();

  if (!fileName) {
    return c.json({ error: 'file_name is required' }, 400);
  }

  const extension = fileName.includes('.') ? fileName.split('.').pop() : 'jpg';
  let scopePath = `${target}`;

  if (target === 'organization') {
    if (!orgSlug) {
      return c.json({ error: 'org_slug is required for organization thumbnails' }, 400);
    }

    const client = createAdminClient();
    const { data: membership } = await client
      .from('org_members')
      .select('org_id')
      .eq('user_id', auth.user!.id)
      .in('role', ['owner', 'admin', 'member']);

    const memberOrgIds = (membership ?? []).map((item: any) => item.org_id);
    const { data: org } = await client
      .from('organizations')
      .select('id, slug')
      .eq('slug', orgSlug)
      .single();

    if (!org || !memberOrgIds.includes(org.id)) {
      return c.json({ error: 'Not authorized to upload organization thumbnails for this org' }, 403);
    }

    scopePath = `organization/${org.slug}`;
  } else if (target === 'personal') {
    if (!username) {
      return c.json({ error: 'Set a username before uploading personal thumbnails' }, 400);
    }
    scopePath = `personal/${username}`;
  } else {
    scopePath = `community/${username || auth.user!.id}`;
  }

  const path = `${auth.user!.id}/${scopePath}/${crypto.randomUUID()}.${extension}`;
  const client = createAdminClient();
  const { data, error } = await client.storage
    .from(REGISTRY_THUMBNAIL_BUCKET)
    .createSignedUploadUrl(path);

  if (error || !data?.token) {
    return c.json({ error: 'Failed to create thumbnail upload target' }, 500);
  }

  return c.json({
    bucket: REGISTRY_THUMBNAIL_BUCKET,
    path,
    token: data.token,
  });
});

// GET /v1/private/content - List accessible private content across personal and org scopes
publishRoutes.get('/private/content', async (c) => {
  const auth = c.get('auth') as AuthContext;
  const { limit, offset } = parsePagination(c.req.query('limit'), c.req.query('offset'));
  const typeFilter = c.req.query('type') as ContentType | undefined;
  const scope = (c.req.query('scope') || 'all') as 'all' | 'personal' | 'organization';
  const query = (c.req.query('q') || '').trim().toLowerCase();

  const client = createAdminClient();

  let accessibleOrgIds: string[] = [];
  if (auth.apiKeyOrgId) {
    accessibleOrgIds = [auth.apiKeyOrgId];
  } else {
    const { data: memberships } = await client
      .from('org_members')
      .select('org_id')
      .eq('user_id', auth.user!.id);
    accessibleOrgIds = (memberships ?? []).map((membership: any) => membership.org_id);
  }

  const rows: any[] = [];

  const allowPersonal = scope === 'all' || scope === 'personal';
  const allowOrg = scope === 'all' || scope === 'organization';
  const canReadPersonal = allowPersonal && !auth.apiKeyOrgId;

  if (canReadPersonal) {
    let personalQuery = client
      .from('content')
      .select('id, type, slug, namespace, visibility, status, version, data, created_at, updated_at, published_at, owner:users!owner_id(display_name, username)')
      .eq('owner_id', auth.user!.id)
      .is('org_id', null)
      .eq('visibility', 'private')
      .eq('status', 'published');

    if (typeFilter && CONTENT_TYPES.includes(typeFilter)) {
      personalQuery = personalQuery.eq('type', typeFilter);
    }

    const { data } = await personalQuery;
    rows.push(...(data ?? []));
  }

  if (allowOrg && accessibleOrgIds.length > 0) {
    let orgQuery = client
      .from('content')
      .select('id, type, slug, namespace, visibility, status, version, data, created_at, updated_at, published_at, owner:users!owner_id(display_name, username)')
      .in('org_id', accessibleOrgIds)
      .eq('visibility', 'private')
      .eq('status', 'published');

    if (typeFilter && CONTENT_TYPES.includes(typeFilter)) {
      orgQuery = orgQuery.eq('type', typeFilter);
    }

    const { data } = await orgQuery;
    rows.push(...(data ?? []));
  }

  const uniqueRows = Array.from(new Map(rows.map((row) => [row.id, row])).values());
  const filteredRows = query
    ? uniqueRows.filter((item) => {
        const name = (item.data as Record<string, unknown> | null | undefined)?.name;
        const description = (item.data as Record<string, unknown> | null | undefined)?.description;
        return (
          item.slug.toLowerCase().includes(query) ||
          item.namespace.toLowerCase().includes(query) ||
          (typeof name === 'string' && name.toLowerCase().includes(query)) ||
          (typeof description === 'string' && description.toLowerCase().includes(query))
        );
      })
    : uniqueRows;

  filteredRows.sort((a, b) => {
    const left = new Date(b.updated_at || b.created_at || 0).getTime();
    const right = new Date(a.updated_at || a.created_at || 0).getTime();
    return left - right;
  });

  const pagedRows = filteredRows.slice(offset, offset + limit);

  const items = await Promise.all(
    pagedRows.map(async (item) => ({
      id: item.id,
      type: item.type,
      slug: item.slug,
      namespace: item.namespace,
      visibility: item.visibility,
      status: item.status,
      version: item.version,
      name: (item.data as Record<string, unknown>)?.name,
      description: (item.data as Record<string, unknown>)?.description,
      created_at: item.created_at,
      updated_at: item.updated_at,
      published_at: item.published_at,
      owner_name: (item as any).owner?.display_name || null,
      owner_username: (item as any).owner?.username || null,
      thumbnail_url: await getSignedThumbnailUrl(item.data as Record<string, unknown> | null | undefined),
      intelligence: null,
    })),
  );

  return c.json({
    total: filteredRows.length,
    limit,
    offset,
    items,
  });
});

// GET /v1/my/content - List own content
publishRoutes.get('/my/content', async (c) => {
  const auth = c.get('auth') as AuthContext;
  const { limit, offset } = parsePagination(c.req.query('limit'), c.req.query('offset'));
  const typeFilter = c.req.query('type') as ContentType | undefined;

  const client = createAdminClient();

  let query = client
    .from('content')
    .select('id, type, slug, namespace, visibility, status, version, data, created_at, updated_at, published_at', { count: 'exact' })
    .eq('owner_id', auth.user!.id)
    .order('updated_at', { ascending: false })
    .range(offset, offset + limit - 1);

  if (typeFilter && CONTENT_TYPES.includes(typeFilter)) {
    query = query.eq('type', typeFilter);
  }

  const { data, error, count } = await query;

  if (error) {
    return c.json({ error: 'Failed to fetch content' }, 500);
  }

  const items = await Promise.all(
    (data ?? []).map(async (item) => ({
      id: item.id,
      type: item.type,
      slug: item.slug,
      namespace: item.namespace,
      visibility: item.visibility,
      status: item.status,
      version: item.version,
      name: (item.data as Record<string, unknown>)?.name,
      description: (item.data as Record<string, unknown>)?.description,
      created_at: item.created_at,
      updated_at: item.updated_at,
      published_at: item.published_at,
      owner_name: auth.user!.display_name ?? null,
      owner_username: auth.user!.username ?? null,
      thumbnail_url: await getSignedThumbnailUrl(item.data as Record<string, unknown> | null | undefined),
      intelligence:
        item.visibility === 'public' && item.status === 'published'
          ? getContentIntelligence(
            item.type,
            item.namespace,
            item.slug,
            item.data as Record<string, unknown> | null | undefined,
          )
          : null,
    })),
  );

  return c.json({
    total: count ?? 0,
    limit,
    offset,
    items,
  });
});

// POST /v1/content - Publish new content
publishRoutes.post('/content', requireApiKeyScope('content:write'), async (c) => {
  const startedAt = Date.now();
  const auth = c.get('auth') as AuthContext;
  const body = await c.req.json();
  const validationTelemetryContext = {
    actorType: 'service' as const,
    anonymousId: 'api:content-validation',
    orgId: undefined,
    userId: undefined,
  };

  // Validate required fields
  if (!body.type || !CONTENT_TYPES.includes(body.type)) {
    return c.json({ error: `type must be one of: ${CONTENT_TYPES.join(', ')}` }, 400);
  }
  if (!body.slug || typeof body.slug !== 'string') {
    return c.json({ error: 'slug is required' }, 400);
  }
  if (!body.version || typeof body.version !== 'string') {
    return c.json({ error: 'version is required' }, 400);
  }
  if (!body.data || typeof body.data !== 'object') {
    return c.json({ error: 'data is required and must be an object' }, 400);
  }

  const contentValidation = validateRegistryContent(body.type, body.data);
  if (!contentValidation.valid) {
    emitApiTelemetry(c, {
      name: 'content.validation.completed',
      context: validationTelemetryContext,
      properties: {
        contentType: body.type,
        durationMs: Date.now() - startedAt,
        errorCount: contentValidation.errors.length,
        itemCount: 1,
        valid: false,
      },
    });
    return c.json({
      error: 'Content data failed registry schema validation',
      validationErrors: contentValidation.errors,
    }, 400);
  }

  // Validate essence content data if the type is an essence document
  // (patterns, themes, etc. have their own data shapes, but
  // if the data looks like an essence document, validate it)
  if (body.data.version && (body.data.platform || body.data.dna)) {
    const validation = validateEssence(body.data);
    if (!validation.valid) {
      emitApiTelemetry(c, {
        name: 'content.validation.completed',
        context: validationTelemetryContext,
        properties: {
          contentType: body.type,
          durationMs: Date.now() - startedAt,
          errorCount: validation.errors.length,
          itemCount: 1,
          valid: false,
        },
      });
      return c.json({
        error: 'Content data failed essence validation',
        validationErrors: validation.errors,
      }, 400);
    }
  }

  emitApiTelemetry(c, {
    name: 'content.validation.completed',
    context: validationTelemetryContext,
    properties: {
      contentType: body.type,
      durationMs: Date.now() - startedAt,
      errorCount: 0,
      itemCount: 1,
      valid: true,
    },
  });

  // Determine visibility
  const visibility = body.visibility === 'private' ? 'private' : 'public';

  // Private content requires Pro+ tier
  if (visibility === 'private' && auth.user!.tier === 'free') {
    emitApiTelemetry(c, {
      name: 'content.publish.completed',
      properties: {
        contentType: body.type,
        durationMs: Date.now() - startedAt,
        errorCode: 'private_content_requires_paid_plan',
        success: false,
        visibility,
      },
    });
    return c.json({ error: 'Private content requires Pro tier or higher' }, 403);
  }

  // Spam prevention: max 5 pending submissions per user
  const client = createAdminClient();

  const { count: pendingCount } = await client
    .from('content')
    .select('*', { count: 'exact', head: true })
    .eq('owner_id', auth.user!.id)
    .eq('status', 'pending');

  if ((pendingCount ?? 0) >= 5) {
    return c.json({ error: 'Too many pending submissions. Wait for existing content to be reviewed.' }, 429);
  }

  // Determine namespace and status
  const requestedNamespace = typeof body.namespace === 'string' ? body.namespace.trim() : '';
  const personalNamespace = `@${auth.user!.username}`;
  let namespace = requestedNamespace || (visibility === 'private' ? personalNamespace : '@community');
  let status: 'pending' | 'approved' | 'rejected' | 'published';

  if (namespace === '@official') {
    return c.json({ error: 'Cannot publish to @official namespace. Use admin sync.' }, 403);
  }

  if (namespace.startsWith('@org:')) {
    return c.json({ error: 'Organization content must be published through the organization route.' }, 400);
  }

  if (namespace === '@community' && visibility === 'private') {
    return c.json({ error: 'Community content cannot be private. Use your personal package namespace instead.' }, 400);
  }

  if (namespace !== '@community' && namespace !== personalNamespace) {
    return c.json({ error: `Personal content must publish to "@community" or your personal namespace "${personalNamespace}".` }, 400);
  }

  const limits = getCommercialLimits(auth.user!.tier, null);
  const { count: personalContentCount } = await client
    .from('content')
    .select('*', { count: 'exact', head: true })
    .eq('owner_id', auth.user!.id)
    .is('org_id', null);

  if (typeof limits.personal_content_items === 'number' && (personalContentCount ?? 0) >= limits.personal_content_items) {
    emitApiTelemetry(c, {
      name: 'content.publish.completed',
      properties: {
        contentType: body.type,
        durationMs: Date.now() - startedAt,
        errorCode: 'personal_content_limit_reached',
        success: false,
        visibility,
      },
    });
    return c.json({ error: 'You have reached your personal content limit for this plan.' }, 403);
  }

  if (visibility === 'private' && typeof limits.personal_private_packages === 'number') {
    const { count: privateCount } = await client
      .from('content')
      .select('*', { count: 'exact', head: true })
      .eq('owner_id', auth.user!.id)
      .is('org_id', null)
      .eq('visibility', 'private');

    if ((privateCount ?? 0) >= limits.personal_private_packages) {
      emitApiTelemetry(c, {
        name: 'content.publish.completed',
        properties: {
          contentType: body.type,
          durationMs: Date.now() - startedAt,
          errorCode: 'personal_private_package_limit_reached',
          success: false,
          visibility,
        },
      });
      return c.json({ error: 'You have reached your private package limit for this plan.' }, 403);
    }
  }

  // Trusted users publish instantly, others go to moderation
  if (auth.user!.trusted) {
    status = 'published';
  } else {
    status = 'pending';
  }

  const { data, error } = await client
    .from('content')
    .insert({
      type: body.type,
      slug: body.slug,
      namespace,
      owner_id: auth.user!.id,
      org_id: body.org_id || null,
      visibility,
      status,
      version: body.version,
      data: body.data,
      published_at: status === 'published' ? new Date().toISOString() : null,
    })
    .select()
    .single();

  if (error) {
    const errorCode = error.code === '23505' ? 'duplicate_content' : 'publish_failed';
    emitApiTelemetry(c, {
      name: 'content.publish.completed',
      properties: {
        contentType: body.type,
        durationMs: Date.now() - startedAt,
        errorCode,
        success: false,
        visibility,
      },
    });
    if (error.code === '23505') {
      return c.json({ error: `Content "${namespace}/${body.type}/${body.slug}" already exists` }, 409);
    }
    return c.json({ error: 'Failed to publish content' }, 500);
  }

  // Also create version history entry
  await client.from('content_versions').insert({
    content_id: data.id,
    version: body.version,
    data: body.data,
    created_by: auth.user!.id,
  });

  await recordAuditEvent({
    actor_user_id: auth.user!.id,
    scope: 'content',
    action: 'personal_content.published',
    target_type: body.type,
    target_id: data.id,
    details: {
      slug: body.slug,
      namespace,
      visibility,
      status,
      version: body.version,
    },
  });

  await recordUsageEvent({
    user_id: auth.user!.id,
    metric: visibility === 'private' ? 'private_package_publish' : 'content_publish',
    quantity: 1,
    source: 'jwt',
  });

  emitApiTelemetry(c, {
    name: 'content.publish.completed',
    properties: {
      contentType: body.type,
      durationMs: Date.now() - startedAt,
      success: true,
      visibility,
    },
  });

  const statusCode = status === 'published' ? 201 : 202;
  return c.json({
    ...data,
    message: status === 'pending'
      ? 'Content submitted for review. It will be published after approval.'
      : 'Content published successfully.',
  }, statusCode);
});

// PATCH /v1/content/:id - Update own content
publishRoutes.patch('/content/:id', requireApiKeyScope('content:write'), async (c) => {
  const auth = c.get('auth') as AuthContext;
  const contentId = c.req.param('id')!;
  const body = await c.req.json();

  const client = createAdminClient();

  // Verify ownership
  const { data: existing } = await client
    .from('content')
    .select('id, owner_id, type')
    .eq('id', contentId)
    .single();

  if (!existing) {
    return c.json({ error: 'Content not found' }, 404);
  }

  if (existing.owner_id !== auth.user!.id) {
    return c.json({ error: 'Not authorized to update this content' }, 403);
  }

  const updates: ContentUpdate = {};
  if (body.data && typeof body.data === 'object') {
    const contentData = body.data as Record<string, unknown>;
    const contentValidation = validateRegistryContent(existing.type as ContentType, contentData);
    if (!contentValidation.valid) {
      return c.json({
        error: 'Content data failed registry schema validation',
        validationErrors: contentValidation.errors,
      }, 400);
    }
    updates.data = contentData;
  }
  if (body.version && typeof body.version === 'string') updates.version = body.version;
  if (body.visibility === 'public' || body.visibility === 'private') updates.visibility = body.visibility;

  if (Object.keys(updates).length === 0) {
    return c.json({ error: 'No valid fields to update' }, 400);
  }

  const { data, error } = await client
    .from('content')
    .update(updates)
    .eq('id', contentId)
    .select()
    .single();

  if (error) {
    return c.json({ error: 'Failed to update content' }, 500);
  }

  // Save version history if data or version changed
  if (updates.data || updates.version) {
    await client.from('content_versions').insert({
      content_id: contentId,
      version: (data.version as string),
      data: (data.data as Record<string, unknown>),
      created_by: auth.user!.id,
    });
  }

  await recordAuditEvent({
    actor_user_id: auth.user!.id,
    scope: 'content',
    action: 'personal_content.updated',
    target_type: existing.type,
    target_id: contentId,
    details: {
      visibility: updates.visibility ?? null,
      version: updates.version ?? null,
      data_updated: Boolean(updates.data),
    },
  });

  return c.json(data);
});

// DELETE /v1/content/:id - Delete own content
publishRoutes.delete('/content/:id', requireApiKeyScope('content:write'), async (c) => {
  const auth = c.get('auth') as AuthContext;
  const contentId = c.req.param('id')!;

  const client = createAdminClient();

  // Verify ownership
  const { data: existing } = await client
    .from('content')
    .select('id, owner_id')
    .eq('id', contentId)
    .single();

  if (!existing) {
    return c.json({ error: 'Content not found' }, 404);
  }

  if (existing.owner_id !== auth.user!.id) {
    return c.json({ error: 'Not authorized to delete this content' }, 403);
  }

  const { error } = await client
    .from('content')
    .delete()
    .eq('id', contentId);

  if (error) {
    return c.json({ error: 'Failed to delete content' }, 500);
  }

  await recordAuditEvent({
    actor_user_id: auth.user!.id,
    scope: 'content',
    action: 'personal_content.deleted',
    target_type: 'content',
    target_id: contentId,
    details: {},
  });

  return c.json({ message: 'Content deleted' });
});

// POST /v1/content/:id/flag - Flag content (deducts 2 reputation points from owner)
publishRoutes.post('/content/:id/flag', requireApiKeyScope('content:write'), async (c) => {
  const auth = c.get('auth') as AuthContext;
  const contentId = c.req.param('id')!;
  const body = await c.req.json().catch(() => ({}));

  const client = createAdminClient();

  // Verify content exists and is published
  const { data: content } = await client
    .from('content')
    .select('id, owner_id, status')
    .eq('id', contentId)
    .single();

  if (!content) {
    return c.json({ error: 'Content not found' }, 404);
  }

  if (content.status !== 'published') {
    return c.json({ error: 'Can only flag published content' }, 400);
  }

  // Prevent self-flagging
  if (content.owner_id === auth.user!.id) {
    return c.json({ error: 'Cannot flag your own content' }, 400);
  }

  // Record the flag (re-queue for moderation)
  await client
    .from('moderation_queue')
    .insert({
      content_id: contentId,
      submitted_by: content.owner_id,
      status: 'pending',
      rejection_reason: body.reason ? `Flagged: ${body.reason}` : 'Flagged by user',
    });

  // Deduct 2 reputation points from content owner
  const { data: owner } = await client
    .from('users')
    .select('id, reputation_score')
    .eq('id', content.owner_id)
    .single();

  if (owner) {
    await client
      .from('users')
      .update({ reputation_score: Math.max(0, owner.reputation_score - 2) })
      .eq('id', content.owner_id);
  }

  return c.json({ message: 'Content flagged for review' });
});
