/**
 * Social Inbox Service
 * Unified inbox for social media messages across platforms
 *
 * @module lib/services/social-inbox.service
 */

import { logger } from '@/lib/logger';
import { decrypt } from '@/lib/encryption';
import {
  socialAccountsAdapter,
  socialMessagesAdapter,
} from '@/lib/adapters/social';
import type {
  SocialPlatform,
  SocialMessageOutput,
  InboxConversation,
  UnreadCounts,
} from '@/lib/types/social-media.types';

const META_API_VERSION = 'v19.0';
const META_GRAPH_URL = `https://graph.facebook.com/${META_API_VERSION}`;

// Cast helpers: allow passing extra args beyond the adapter's narrow type signatures
const messagesAdapter = socialMessagesAdapter as unknown as {
  findMany(args: Record<string, unknown>): Promise<unknown[]>;
  findFirst(args: Record<string, unknown>): Promise<unknown>;
  count(args: Record<string, unknown>): Promise<number>;
  create(args: Record<string, unknown>): Promise<unknown>;
  updateMany(args: Record<string, unknown>): Promise<{ count: number }>;
};

export class SocialInboxService {
  /**
   * List inbox conversations across all connected accounts
   */
  static async getConversations(userId: string): Promise<InboxConversation[]> {
    const accounts = await socialAccountsAdapter.findMany({
      where: { userId, status: 'CONNECTED' },
      select: { id: true, platform: true, accountName: true },
    });

    if (accounts.length === 0) return [];

    const accountIds = (accounts as Array<Record<string, unknown>>).map((a) => a.id as string);
    const accountMap = new Map(
      (accounts as Array<Record<string, unknown>>).map((a) => [a.id as string, a])
    );

    // Get all messages for user's accounts, then group by conversation in JS
    // (The Convex adapter stubs $queryRaw to return [], so we use findMany instead)
    const allMessages = await messagesAdapter.findMany({
      where: {
        socialAccountId: { in: accountIds },
      },
      orderBy: { createdAt: 'desc' },
    }) as Array<Record<string, unknown>>;

    // Group by conversationId + socialAccountId, keeping only the latest message per group
    const conversationMap = new Map<string, { msg: Record<string, unknown>; maxCreatedAt: Date }>();
    for (const msg of allMessages) {
      const key = `${msg.conversationId as string}::${msg.socialAccountId as string}`;
      const createdAt = new Date(msg.createdAt as string | number | Date);
      const existing = conversationMap.get(key);
      if (!existing || createdAt > existing.maxCreatedAt) {
        conversationMap.set(key, { msg, maxCreatedAt: createdAt });
      }
    }

    // Sort groups by latest message date descending
    const sortedGroups = Array.from(conversationMap.values())
      .sort((a, b) => b.maxCreatedAt.getTime() - a.maxCreatedAt.getTime());

    const conversations: InboxConversation[] = [];

    for (const group of sortedGroups) {
      const lastMsg = group.msg;
      const conversationId = lastMsg.conversationId as string;
      const socialAccountId = lastMsg.socialAccountId as string;

      const account = accountMap.get(socialAccountId);
      if (!account) continue;

      const unreadCount = await messagesAdapter.count({
        where: {
          conversationId,
          socialAccountId,
          isRead: false,
          direction: 'INBOUND',
        },
      });

      // Find the first inbound message to get participant info
      const participant = await messagesAdapter.findFirst({
        where: {
          conversationId,
          socialAccountId,
          direction: 'INBOUND',
        },
        select: {
          senderName: true,
          senderHandle: true,
          senderAvatarUrl: true,
        },
        orderBy: { createdAt: 'asc' },
      });

      const participantRecord = participant as Record<string, unknown> | null;

      conversations.push({
        conversationId,
        platform: account.platform as SocialPlatform,
        accountName: account.accountName as string,
        lastMessage: lastMsg as unknown as SocialMessageOutput,
        unreadCount,
        participantName: (participantRecord?.senderName as string) ?? null,
        participantHandle: (participantRecord?.senderHandle as string) ?? null,
        participantAvatarUrl: (participantRecord?.senderAvatarUrl as string) ?? null,
      });
    }

    return conversations;
  }

  /**
   * Get messages in a specific conversation
   */
  static async getConversationMessages(
    userId: string,
    conversationId: string
  ): Promise<SocialMessageOutput[]> {
    // Verify the conversation belongs to one of the user's accounts
    const accounts = await socialAccountsAdapter.findMany({
      where: { userId },
      select: { id: true },
    });

    const accountIds = (accounts as Array<Record<string, unknown>>).map((a) => a.id as string);

    const messages = await messagesAdapter.findMany({
      where: {
        conversationId,
        socialAccountId: { in: accountIds },
      },
      orderBy: { createdAt: 'asc' },
    });

    return messages as unknown as SocialMessageOutput[];
  }

  /**
   * Send a reply to a conversation via Meta Graph API
   */
  static async sendReply(
    userId: string,
    conversationId: string,
    content: string,
    mediaUrls?: string[]
  ): Promise<SocialMessageOutput> {
    // Find the conversation's account
    const existingMessage = await messagesAdapter.findFirst({
      where: { conversationId },
      include: { socialAccount: true },
    });

    if (!existingMessage) {
      throw new Error('Conversation not found');
    }

    const existingRecord = existingMessage as unknown as {
      socialAccount: {
        userId: string;
        id: string;
        platform: string;
        accessToken: string;
        platformAccountId: string;
      };
    };

    // Verify ownership
    if (existingRecord.socialAccount.userId !== userId) {
      throw new Error('Unauthorized');
    }

    const account = existingRecord.socialAccount;
    const accessToken = decrypt(account.accessToken);

    // Send via Meta API
    if (account.platform === 'FACEBOOK' || account.platform === 'INSTAGRAM') {
      try {
        const response = await fetch(
          `${META_GRAPH_URL}/${conversationId}/messages`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              message: content,
              access_token: accessToken,
            }),
          }
        );

        if (!response.ok) {
          const errData = await response.json();
          logger.error('Meta reply failed', { error: errData });
          throw new Error(errData.error?.message || 'Failed to send reply');
        }

        const data = await response.json();

        // Store outbound message
        const saved = await messagesAdapter.create({
          data: {
            socialAccountId: account.id,
            platform: account.platform,
            direction: 'OUTBOUND',
            conversationId,
            externalMessageId: data.id || null,
            content,
            mediaUrls: mediaUrls || [],
            isRead: true,
          },
        });

        logger.info('Reply sent', { conversationId, platform: account.platform });
        return saved as unknown as SocialMessageOutput;
      } catch (error) {
        logger.error('Failed to send reply', { error, conversationId });
        throw error;
      }
    }

    throw new Error(`Reply not supported for platform ${account.platform}`);
  }

  /**
   * Sync inbox messages from Meta accounts
   */
  static async syncInbox(userId: string): Promise<{ synced: number }> {
    const accounts = await socialAccountsAdapter.findMany({
      where: { userId, status: 'CONNECTED', platform: { in: ['FACEBOOK', 'INSTAGRAM'] } },
    });

    let totalSynced = 0;

    for (const account of accounts as Array<Record<string, unknown>>) {
      try {
        const accessToken = decrypt(account.accessToken as string);
        const pageId = account.platformAccountId as string;

        // Fetch conversations from Meta
        const convResponse = await fetch(
          `${META_GRAPH_URL}/${pageId}/conversations?fields=id,updated_time,participants,messages{id,message,from,created_time}&access_token=${accessToken}`
        );

        if (!convResponse.ok) {
          logger.warn('Failed to fetch conversations for sync', {
            accountId: account.id,
            platform: account.platform,
          });
          continue;
        }

        const convData = await convResponse.json();

        for (const conv of convData.data || []) {
          for (const msg of conv.messages?.data || []) {
            // Check if message already exists
            const exists = await messagesAdapter.findFirst({
              where: {
                socialAccountId: account.id as string,
                externalMessageId: msg.id,
              },
            });

            if (exists) continue;

            const isInbound = msg.from?.id !== pageId;

            await messagesAdapter.create({
              data: {
                socialAccountId: account.id as string,
                platform: account.platform as string,
                direction: isInbound ? 'INBOUND' : 'OUTBOUND',
                conversationId: conv.id,
                externalMessageId: msg.id,
                senderName: msg.from?.name || null,
                senderHandle: null,
                content: msg.message || '',
                mediaUrls: [],
                isRead: !isInbound,
                createdAt: msg.created_time ? new Date(msg.created_time) : new Date(),
              },
            });

            totalSynced++;
          }
        }

        logger.info('Inbox synced for account', {
          accountId: account.id,
          platform: account.platform,
        });
      } catch (error) {
        logger.error('Inbox sync error for account', {
          accountId: account.id,
          error,
        });
      }
    }

    return { synced: totalSynced };
  }

  /**
   * Get unread message counts by platform and account
   * Uses findMany + JS aggregation (the Convex adapter stubs groupBy to return [])
   */
  static async getUnreadCounts(userId: string): Promise<UnreadCounts> {
    const accounts = await socialAccountsAdapter.findMany({
      where: { userId },
      select: { id: true, platform: true, accountName: true },
    });

    const accountIds = (accounts as Array<Record<string, unknown>>).map((a) => a.id as string);

    // Fetch all unread inbound messages for these accounts
    const unreadMessages = await messagesAdapter.findMany({
      where: {
        socialAccountId: { in: accountIds },
        isRead: false,
        direction: 'INBOUND',
      },
      select: { id: true, socialAccountId: true },
    }) as Array<Record<string, unknown>>;

    // Aggregate counts by socialAccountId in JS
    const countByAccount = new Map<string, number>();
    for (const msg of unreadMessages) {
      const accountId = msg.socialAccountId as string;
      countByAccount.set(accountId, (countByAccount.get(accountId) || 0) + 1);
    }

    const byPlatform: Record<string, number> = {};
    const byAccount: Record<string, number> = {};
    let total = 0;

    const accountsArr = accounts as Array<Record<string, unknown>>;
    for (const [accountId, count] of countByAccount) {
      const account = accountsArr.find((a) => a.id === accountId);
      if (!account) continue;

      total += count;
      byPlatform[account.platform as string] = (byPlatform[account.platform as string] || 0) + count;
      byAccount[account.id as string] = count;
    }

    return {
      total,
      byPlatform: byPlatform as Record<SocialPlatform, number>,
      byAccount,
    };
  }

  /**
   * Mark messages or entire conversation as read
   */
  static async markAsRead(
    userId: string,
    messageIds?: string[],
    conversationId?: string
  ): Promise<{ updated: number }> {
    const accounts = await socialAccountsAdapter.findMany({
      where: { userId },
      select: { id: true },
    });

    const accountIds = (accounts as Array<Record<string, unknown>>).map((a) => a.id as string);

    if (messageIds && messageIds.length > 0) {
      const result = await messagesAdapter.updateMany({
        where: {
          id: { in: messageIds },
          socialAccountId: { in: accountIds },
        },
        data: { isRead: true },
      });
      return { updated: result.count };
    }

    if (conversationId) {
      const result = await messagesAdapter.updateMany({
        where: {
          conversationId,
          socialAccountId: { in: accountIds },
          isRead: false,
        },
        data: { isRead: true },
      });
      return { updated: result.count };
    }

    return { updated: 0 };
  }
}

export const socialInbox = new SocialInboxService();
