/**
 * Broadcast repository — Cosmos DB CRUD + targeting queries
 * @module broadcasts/repository
 */

import { getContainer } from '../../lib/cosmos.js';
import {
  Broadcast,
  BroadcastDelivery,
  InAppMessage,
  BroadcastRead,
  type BroadcastStatus,
} from './types.js';

// =============================================================================
// Broadcast CRUD
// =============================================================================

export async function createBroadcast(doc: Broadcast): Promise<Broadcast> {
  const container = getContainer('broadcasts');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to create broadcast');
  return resource as unknown as Broadcast;
}

export async function getBroadcast(id: string, productId: string): Promise<Broadcast | null> {
  const container = getContainer('broadcasts');
  try {
    const { resource } = await container.item(id, productId).read();
    return resource as unknown as Broadcast | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function listBroadcasts(
  productId: string,
  options?: { status?: BroadcastStatus; limit?: number; offset?: number }
): Promise<{ broadcasts: Broadcast[]; total: number }> {
  const container = getContainer('broadcasts');

  let query = 'SELECT * FROM c WHERE c.productId = @productId';
  const parameters = [{ name: '@productId', value: productId }];

  if (options?.status) {
    query += ' AND c.status = @status';
    parameters.push({ name: '@status', value: options.status });
  }

  query += ' ORDER BY c.createdAt DESC';

  // Get total count
  const countQuery = query.replace('SELECT *', 'SELECT VALUE COUNT(1)');
  const { resources: countResult } = await container.items
    .query<number>({ query: countQuery, parameters })
    .fetchAll();
  const total = countResult[0] ?? 0;

  // Add pagination
  if (options?.offset) {
    query += ` OFFSET ${options.offset}`;
  }
  if (options?.limit) {
    query += ` LIMIT ${options.limit}`;
  }

  const { resources } = await container.items.query<Broadcast>({ query, parameters }).fetchAll();

  return { broadcasts: resources, total };
}

export async function updateBroadcast(
  id: string,
  productId: string,
  updates: Partial<Broadcast>
): Promise<Broadcast | null> {
  const container = getContainer('broadcasts');

  const existing = await getBroadcast(id, productId);
  if (!existing) return null;

  const updated: Broadcast = {
    ...existing,
    ...updates,
    id: existing.id,
    productId: existing.productId,
    updatedAt: new Date().toISOString(),
  };

  const { resource } = await container.items.upsert(updated);
  if (!resource) throw new Error('Failed to update broadcast');
  return resource as unknown as Broadcast;
}

export async function deleteBroadcast(id: string, productId: string): Promise<boolean> {
  const container = getContainer('broadcasts');
  try {
    await container.item(id, productId).delete();
    return true;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return false;
    throw err;
  }
}

export async function updateBroadcastMetrics(
  id: string,
  productId: string,
  increments: Partial<Broadcast['metrics']>
): Promise<void> {
  const container = getContainer('broadcasts');

  const existing = await getBroadcast(id, productId);
  if (!existing) return;

  // Increment metrics properly
  const updatedMetrics: Broadcast['metrics'] = {
    targetedCount: existing.metrics.targetedCount + (increments.targetedCount ?? 0),
    sentCount: existing.metrics.sentCount + (increments.sentCount ?? 0),
    deliveredCount: existing.metrics.deliveredCount + (increments.deliveredCount ?? 0),
    openedCount: existing.metrics.openedCount + (increments.openedCount ?? 0),
    clickedCount: existing.metrics.clickedCount + (increments.clickedCount ?? 0),
    dismissedCount: existing.metrics.dismissedCount + (increments.dismissedCount ?? 0),
    convertedCount: existing.metrics.convertedCount + (increments.convertedCount ?? 0),
  };

  const updated: Broadcast = {
    ...existing,
    metrics: updatedMetrics,
    updatedAt: new Date().toISOString(),
  };

  await container.items.upsert(updated);
}

// =============================================================================
// Delivery Tracking (per-user)
// =============================================================================

export async function createDelivery(doc: BroadcastDelivery): Promise<BroadcastDelivery> {
  const container = getContainer('broadcast_deliveries');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to create delivery');
  return resource as unknown as BroadcastDelivery;
}

export async function getDelivery(
  broadcastId: string,
  userId: string
): Promise<BroadcastDelivery | null> {
  const container = getContainer('broadcast_deliveries');
  const id = `${broadcastId}:${userId}`;
  try {
    const { resource } = await container.item(id, userId).read();
    return resource as unknown as BroadcastDelivery | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function updateDelivery(
  broadcastId: string,
  userId: string,
  updates: Partial<BroadcastDelivery>
): Promise<void> {
  const container = getContainer('broadcast_deliveries');
  const id = `${broadcastId}:${userId}`;

  try {
    const { resource: existing } = await container.item(id, userId).read();
    if (!existing) return;

    const updated: BroadcastDelivery = {
      ...(existing as BroadcastDelivery),
      ...updates,
      id,
      broadcastId,
      userId,
      updatedAt: new Date().toISOString(),
    };

    await container.items.upsert(updated);
  } catch (err) {
    if ((err as { code?: number }).code !== 404) throw err;
  }
}

export async function listDeliveriesForBroadcast(
  broadcastId: string,
  options?: { status?: string; limit?: number; offset?: number }
): Promise<{ deliveries: BroadcastDelivery[]; total: number }> {
  const container = getContainer('broadcast_deliveries');

  let query = 'SELECT * FROM c WHERE c.broadcastId = @broadcastId';
  const parameters = [{ name: '@broadcastId', value: broadcastId }];

  if (options?.status) {
    query += ' AND (c.pushStatus = @status OR c.inAppStatus = @status OR c.emailStatus = @status)';
    parameters.push({ name: '@status', value: options.status });
  }

  query += ' ORDER BY c.createdAt DESC';

  const countQuery = query.replace('SELECT *', 'SELECT VALUE COUNT(1)');
  const { resources: countResult } = await container.items
    .query<number>({ query: countQuery, parameters })
    .fetchAll();
  const total = countResult[0] ?? 0;

  if (options?.limit) {
    query += ` OFFSET ${options.offset ?? 0} LIMIT ${options.limit}`;
  }

  const { resources } = await container.items
    .query<BroadcastDelivery>({ query, parameters })
    .fetchAll();

  return { deliveries: resources, total };
}

// =============================================================================
// In-App Message Queue (per-user)
// =============================================================================

export async function queueInAppMessage(doc: InAppMessage): Promise<InAppMessage> {
  const container = getContainer('in_app_messages');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to queue in-app message');
  return resource as unknown as InAppMessage;
}

export async function getInAppMessagesForUser(
  userId: string,
  productId: string,
  options?: { status?: 'unread' | 'read' | 'dismissed' }
): Promise<InAppMessage[]> {
  const container = getContainer('in_app_messages');

  let query = 'SELECT * FROM c WHERE c.userId = @userId AND c.productId = @productId';
  const parameters = [
    { name: '@userId', value: userId },
    { name: '@productId', value: productId },
  ];

  if (options?.status) {
    query += ' AND c.status = @status';
    parameters.push({ name: '@status', value: options.status });
  }

  // Only show non-expired messages
  query += ' AND (c.expiresAt = null OR c.expiresAt > @now)';
  parameters.push({ name: '@now', value: new Date().toISOString() });

  query += ' ORDER BY c.createdAt DESC';

  const { resources } = await container.items.query<InAppMessage>({ query, parameters }).fetchAll();

  return resources;
}

export async function updateInAppMessageStatus(
  messageId: string,
  userId: string,
  status: 'read' | 'dismissed'
): Promise<void> {
  const container = getContainer('in_app_messages');

  try {
    const { resource: existing } = await container.item(messageId, userId).read();
    if (!existing) return;

    const updated: InAppMessage = {
      ...(existing as InAppMessage),
      status,
      updatedAt: new Date().toISOString(),
    };

    await container.items.upsert(updated);
  } catch (err) {
    if ((err as { code?: number }).code !== 404) throw err;
  }
}

export async function deleteExpiredInAppMessages(userId: string): Promise<number> {
  const container = getContainer('in_app_messages');
  const now = new Date().toISOString();

  const query = `
    SELECT c.id FROM c 
    WHERE c.userId = @userId 
    AND c.expiresAt != null 
    AND c.expiresAt < @now
  `;
  const parameters = [
    { name: '@userId', value: userId },
    { name: '@now', value: now },
  ];

  const { resources } = await container.items
    .query<{ id: string }>({ query, parameters })
    .fetchAll();

  let deleted = 0;
  for (const { id } of resources) {
    try {
      await container.item(id, userId).delete();
      deleted++;
    } catch {
      // Ignore delete errors
    }
  }

  return deleted;
}

// =============================================================================
// Read Receipts
// =============================================================================

export async function recordReadReceipt(
  broadcastId: string,
  userId: string,
  productId: string,
  action: 'read' | 'click' | 'dismiss'
): Promise<boolean> {
  const container = getContainer('broadcast_reads');
  const id = `${broadcastId}:${userId}`;
  const now = new Date().toISOString();

  try {
    const { resource: existing } = await container.item(id, userId).read();

    if (existing) {
      const receipt = existing as BroadcastRead;
      const alreadyRecorded =
        (action === 'read' && !!receipt.readAt) ||
        (action === 'click' && !!receipt.clickedAt) ||
        (action === 'dismiss' && !!receipt.dismissedAt);
      if (alreadyRecorded) {
        return false;
      }
      const updates: Partial<BroadcastRead> & { updatedAt: string } = {
        updatedAt: now,
      };
      if (action === 'read') updates.readAt = now;
      if (action === 'click') updates.clickedAt = now;
      if (action === 'dismiss') updates.dismissedAt = now;

      await container.items.upsert({
        ...receipt,
        ...updates,
      });
      return true;
    } else {
      const receipt: BroadcastRead = {
        id,
        broadcastId,
        userId,
        productId,
        readAt: action === 'read' ? now : undefined,
        clickedAt: action === 'click' ? now : undefined,
        dismissedAt: action === 'dismiss' ? now : undefined,
        createdAt: now,
      };
      await container.items.create(receipt);
      return true;
    }
  } catch (err) {
    if ((err as { code?: number }).code === 404) {
      // Create new receipt
      const receipt: BroadcastRead = {
        id,
        broadcastId,
        userId,
        productId,
        readAt: action === 'read' ? now : undefined,
        clickedAt: action === 'click' ? now : undefined,
        dismissedAt: action === 'dismiss' ? now : undefined,
        createdAt: now,
      };
      await container.items.create(receipt);
      return true;
    } else {
      throw err;
    }
  }
}

// =============================================================================
// Targeting Engine (for reach estimation)
// =============================================================================

export async function estimateTargetReach(
  productId: string,
  target: {
    userSegments?: string[];
    platforms?: string[];
    countryCodes?: string[];
    percentageRollout?: number;
    specificUserIds?: string[];
  }
): Promise<{ count: number; sampleUserIds: string[] }> {
  if (target.specificUserIds && target.specificUserIds.length > 0) {
    return {
      count: target.specificUserIds.length,
      sampleUserIds: target.specificUserIds.slice(0, 5),
    };
  }

  // Query real user count from auth module
  const { list: listUsers } = await import('../auth/repository.js');
  const users = await listUsers(productId, 5000);

  // Apply percentage rollout if specified
  let matchedCount = users.length;
  if (target.percentageRollout && target.percentageRollout < 100) {
    matchedCount = Math.ceil(users.length * (target.percentageRollout / 100));
  }

  // Sample up to 5 user IDs for preview
  const sampleUserIds = users.slice(0, 5).map(u => u.id);

  return {
    count: matchedCount,
    sampleUserIds,
  };
}
