import webpush from 'web-push';
import { getDatabase } from './db';
import * as schema from '@shared/schema';
import { eq, and } from 'drizzle-orm';

// Types
export interface PushSubscriptionData {
  endpoint: string;
  keys: {
    p256dh: string;
    auth: string;
  };
}

export interface PushNotificationPayload {
  title: string;
  body: string;
  icon?: string;
  badge?: string;
  tag?: string;
  data?: Record<string, unknown>;
}

// VAPID keys management
let vapidConfigured = false;

export async function initializeVapid(): Promise<void> {
  const db = getDatabase();
  if (!db) {
    console.log('[Push] Database not available, skipping VAPID initialization');
    return;
  }

  try {
    // Try to get existing VAPID keys from system settings
    const publicKeyResult = await db.select()
      .from(schema.systemSettings)
      .where(eq(schema.systemSettings.key, 'vapid_public_key'));

    const privateKeyResult = await db.select()
      .from(schema.systemSettings)
      .where(eq(schema.systemSettings.key, 'vapid_private_key'));

    let publicKey = publicKeyResult[0]?.value;
    let privateKey = privateKeyResult[0]?.value;

    // Generate new keys if they don't exist
    if (!publicKey || !privateKey) {
      console.log('[Push] Generating new VAPID keys...');
      const keys = webpush.generateVAPIDKeys();
      publicKey = keys.publicKey;
      privateKey = keys.privateKey;

      // Store keys in system settings
      await db.insert(schema.systemSettings)
        .values({ key: 'vapid_public_key', value: publicKey })
        .onConflictDoUpdate({
          target: schema.systemSettings.key,
          set: { value: publicKey }
        });

      await db.insert(schema.systemSettings)
        .values({ key: 'vapid_private_key', value: privateKey })
        .onConflictDoUpdate({
          target: schema.systemSettings.key,
          set: { value: privateKey }
        });

      console.log('[Push] VAPID keys generated and stored');
    }

    // Configure web-push with VAPID keys
    const vapidEmail = process.env.VAPID_EMAIL || 'mailto:admin@taskflow.app';
    webpush.setVapidDetails(vapidEmail, publicKey, privateKey);
    vapidConfigured = true;
    console.log('[Push] VAPID configured successfully');
  } catch (error) {
    console.error('[Push] Failed to initialize VAPID:', error);
  }
}

export async function getVapidPublicKey(): Promise<string | null> {
  const db = getDatabase();
  if (!db) return null;

  try {
    const result = await db.select()
      .from(schema.systemSettings)
      .where(eq(schema.systemSettings.key, 'vapid_public_key'));

    return result[0]?.value || null;
  } catch (error) {
    console.error('[Push] Failed to get VAPID public key:', error);
    return null;
  }
}

// Subscription management
export async function savePushSubscription(
  userId: string,
  subscription: PushSubscriptionData,
  userAgent?: string
): Promise<boolean> {
  const db = getDatabase();
  if (!db) return false;

  try {
    // Check if subscription already exists
    const existing = await db.select()
      .from(schema.pushSubscriptions)
      .where(eq(schema.pushSubscriptions.endpoint, subscription.endpoint));

    if (existing[0]) {
      // Update existing subscription
      await db.update(schema.pushSubscriptions)
        .set({
          userId,
          p256dh: subscription.keys.p256dh,
          auth: subscription.keys.auth,
          userAgent,
          lastUsedAt: new Date()
        })
        .where(eq(schema.pushSubscriptions.endpoint, subscription.endpoint));
    } else {
      // Create new subscription
      await db.insert(schema.pushSubscriptions)
        .values({
          userId,
          endpoint: subscription.endpoint,
          p256dh: subscription.keys.p256dh,
          auth: subscription.keys.auth,
          userAgent
        });
    }

    console.log(`[Push] Saved subscription for user ${userId}`);
    return true;
  } catch (error) {
    console.error('[Push] Failed to save subscription:', error);
    return false;
  }
}

export async function removePushSubscription(endpoint: string): Promise<boolean> {
  const db = getDatabase();
  if (!db) return false;

  try {
    await db.delete(schema.pushSubscriptions)
      .where(eq(schema.pushSubscriptions.endpoint, endpoint));

    console.log('[Push] Removed subscription');
    return true;
  } catch (error) {
    console.error('[Push] Failed to remove subscription:', error);
    return false;
  }
}

export async function removeUserSubscriptions(userId: string): Promise<boolean> {
  const db = getDatabase();
  if (!db) return false;

  try {
    await db.delete(schema.pushSubscriptions)
      .where(eq(schema.pushSubscriptions.userId, userId));

    console.log(`[Push] Removed all subscriptions for user ${userId}`);
    return true;
  } catch (error) {
    console.error('[Push] Failed to remove user subscriptions:', error);
    return false;
  }
}

export async function getUserSubscriptions(userId: string): Promise<schema.PushSubscription[]> {
  const db = getDatabase();
  if (!db) return [];

  try {
    return await db.select()
      .from(schema.pushSubscriptions)
      .where(eq(schema.pushSubscriptions.userId, userId));
  } catch (error) {
    console.error('[Push] Failed to get user subscriptions:', error);
    return [];
  }
}

// Send push notification
export async function sendPushNotification(
  userId: string,
  payload: PushNotificationPayload
): Promise<{ sent: number; failed: number }> {
  if (!vapidConfigured) {
    console.log('[Push] VAPID not configured, skipping notification');
    return { sent: 0, failed: 0 };
  }

  const subscriptions = await getUserSubscriptions(userId);

  if (subscriptions.length === 0) {
    console.log(`[Push] No subscriptions for user ${userId}`);
    return { sent: 0, failed: 0 };
  }

  const notificationPayload = JSON.stringify({
    title: payload.title,
    body: payload.body,
    icon: payload.icon || '/icon-256.png',
    badge: payload.badge || '/icon-128.png',
    tag: payload.tag || `taskflow-${Date.now()}`,
    data: payload.data || {}
  });

  let sent = 0;
  let failed = 0;

  for (const subscription of subscriptions) {
    try {
      await webpush.sendNotification(
        {
          endpoint: subscription.endpoint,
          keys: {
            p256dh: subscription.p256dh,
            auth: subscription.auth
          }
        },
        notificationPayload
      );

      // Update last used timestamp
      const db = getDatabase();
      if (db) {
        await db.update(schema.pushSubscriptions)
          .set({ lastUsedAt: new Date() })
          .where(eq(schema.pushSubscriptions.id, subscription.id));
      }

      sent++;
    } catch (error: any) {
      console.error(`[Push] Failed to send to subscription ${subscription.id}:`, error.message);

      // Remove invalid subscriptions (410 Gone or 404 Not Found)
      if (error.statusCode === 410 || error.statusCode === 404) {
        await removePushSubscription(subscription.endpoint);
        console.log('[Push] Removed invalid subscription');
      }

      failed++;
    }
  }

  console.log(`[Push] Sent ${sent} notifications, ${failed} failed for user ${userId}`);
  return { sent, failed };
}

// Send notification to multiple users
export async function sendPushNotificationToUsers(
  userIds: string[],
  payload: PushNotificationPayload
): Promise<{ sent: number; failed: number }> {
  let totalSent = 0;
  let totalFailed = 0;

  for (const userId of userIds) {
    const result = await sendPushNotification(userId, payload);
    totalSent += result.sent;
    totalFailed += result.failed;
  }

  return { sent: totalSent, failed: totalFailed };
}

// Task notification helpers
export async function sendTaskReminderNotification(
  userId: string,
  taskTitle: string,
  type: 'upcoming' | 'overdue'
): Promise<void> {
  const payload: PushNotificationPayload = {
    title: type === 'upcoming' ? `Upcoming: ${taskTitle}` : `Overdue: ${taskTitle}`,
    body: type === 'upcoming'
      ? 'This task is due soon!'
      : 'This task is now overdue!',
    tag: `task-${type}-${Date.now()}`,
    data: { type, taskTitle }
  };

  await sendPushNotification(userId, payload);
}
