import axios from 'axios';
import { logger } from '../utils/logger.js';
import { db } from '../config/database.js';
import { emails, contacts } from '../db/schema.js';
import { eq, and, or, desc, sql } from 'drizzle-orm';
import { decryptPassword } from '../utils/password.js';

/**
 * JMAP Service - integrácia s Truemail.sk JMAP serverom
 * Syncuje emaily, označuje ako prečítané, posiela odpovede
 */

/**
 * Get JMAP configuration for user (legacy - for backward compatibility)
 */
export const getJmapConfig = (user) => {
  if (!user.email || !user.emailPassword || !user.jmapAccountId) {
    throw new Error('User nemá nastavený email account');
  }

  // Decrypt email password for JMAP API
  const decryptedPassword = decryptPassword(user.emailPassword);

  return {
    server: process.env.JMAP_SERVER || 'https://mail.truemail.sk/jmap/',
    username: user.email,
    password: decryptedPassword,
    accountId: user.jmapAccountId,
  };
};

/**
 * Get JMAP configuration from email account object
 * NOTE: Expects emailPassword to be already decrypted (from getEmailAccountWithCredentials)
 */
export const getJmapConfigFromAccount = (emailAccount) => {
  if (!emailAccount.email || !emailAccount.emailPassword || !emailAccount.jmapAccountId) {
    throw new Error('Email account je neúplný');
  }

  // Password is already decrypted by getEmailAccountWithCredentials
  return {
    server: process.env.JMAP_SERVER || 'https://mail.truemail.sk/jmap/',
    username: emailAccount.email,
    password: emailAccount.emailPassword,
    accountId: emailAccount.jmapAccountId,
  };
};

/**
 * Make JMAP API request
 */
export const jmapRequest = async (jmapConfig, methodCalls) => {
  try {
    const response = await axios.post(
      jmapConfig.server,
      {
        using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'],
        methodCalls,
      },
      {
        auth: {
          username: jmapConfig.username,
          password: jmapConfig.password,
        },
        headers: {
          'Content-Type': 'application/json',
        },
      }
    );
    return response.data;
  } catch (error) {
    logger.error('JMAP request failed', error);
    throw new Error(`JMAP request failed: ${error.message}`);
  }
};

/**
 * Get user's mailboxes (Inbox, Sent, etc.)
 */
export const getMailboxes = async (jmapConfig) => {
  try {
    const response = await jmapRequest(jmapConfig, [
      [
        'Mailbox/get',
        {
          accountId: jmapConfig.accountId,
        },
        'mailbox1',
      ],
    ]);

    return response.methodResponses[0][1].list;
  } catch (error) {
    logger.error('Failed to get mailboxes', error);
    throw error;
  }
};

/**
 * Get user identities (for sending emails)
 */
export const getIdentities = async (jmapConfig) => {
  try {
    const response = await jmapRequest(jmapConfig, [
      [
        'Identity/get',
        {
          accountId: jmapConfig.accountId,
        },
        'identity1',
      ],
    ]);

    return response.methodResponses[0][1].list;
  } catch (error) {
    logger.error('Failed to get identities', error);
    throw error;
  }
};

/**
 * Discover potential contacts from JMAP (no DB storage)
 * Returns list of unique senders
 */
export const discoverContactsFromJMAP = async (jmapConfig, emailAccountId, searchTerm = '', limit = 50) => {
  try {
    logger.info(`Discovering contacts from JMAP (search: "${searchTerm}")`);

    // Query emails, sorted by date
    const queryResponse = await jmapRequest(jmapConfig, [
      [
        'Email/query',
        {
          accountId: jmapConfig.accountId,
          filter: searchTerm
            ? {
                operator: 'OR',
                conditions: [{ from: searchTerm }, { subject: searchTerm }],
              }
            : undefined,
          sort: [{ property: 'receivedAt', isAscending: false }],
          limit: 200, // Fetch more to get diverse senders
        },
        'query1',
      ],
    ]);

    const emailIds = queryResponse.methodResponses?.[0]?.[1]?.ids;

    if (!emailIds || emailIds.length === 0) {
      return [];
    }

    // Fetch email metadata (no bodies)
    const getResponse = await jmapRequest(jmapConfig, [
      [
        'Email/get',
        {
          accountId: jmapConfig.accountId,
          ids: emailIds,
          properties: ['from', 'subject', 'receivedAt', 'preview'],
        },
        'get1',
      ],
    ]);

    const emailsList = getResponse.methodResponses[0][1].list;

    // Get existing contacts for this email account
    const existingContacts = await db
      .select()
      .from(contacts)
      .where(eq(contacts.emailAccountId, emailAccountId));

    const contactEmailsSet = new Set(existingContacts.map((c) => c.email.toLowerCase()));

    // Group by sender (unique senders)
    const sendersMap = new Map();
    const myEmail = jmapConfig.username.toLowerCase();

    emailsList.forEach((email) => {
      const fromEmail = email.from?.[0]?.email;

      if (!fromEmail || fromEmail.toLowerCase() === myEmail) {
        return; // Skip my own emails
      }

      // Keep only the most recent email from each sender
      if (!sendersMap.has(fromEmail)) {
        sendersMap.set(fromEmail, {
          email: fromEmail,
          name: email.from?.[0]?.name || fromEmail.split('@')[0],
          latestSubject: email.subject || '(No Subject)',
          latestDate: email.receivedAt,
          snippet: email.preview || '',
          isContact: contactEmailsSet.has(fromEmail.toLowerCase()),
        });
      }
    });

    const senders = Array.from(sendersMap.values())
      .sort((a, b) => new Date(b.latestDate) - new Date(a.latestDate))
      .slice(0, limit);

    logger.success(`Found ${senders.length} unique senders`);
    return senders;
  } catch (error) {
    logger.error('Failed to discover contacts from JMAP', error);
    throw error;
  }
};

/**
 * Search emails using JMAP full-text search
 * Searches in: from, to, subject, and email body
 * Returns list of unique senders grouped by email address
 */
export const searchEmailsJMAP = async (jmapConfig, emailAccountId, query, limit = 50, offset = 0) => {
  try {
    logger.info(`Searching emails in JMAP (query: "${query}", limit: ${limit}, offset: ${offset})`);

    if (!query || query.trim().length < 1) {
      return [];
    }

    // Use JMAP search with wildcards for substring matching
    // Add wildcards (*) to enable partial matching: "ander" -> "*ander*"
    const wildcardQuery = query.includes('*') ? query : `*${query}*`;

    let queryResponse;

    try {
      // Try with 'text' filter first (full-text search if supported)
      queryResponse = await jmapRequest(jmapConfig, [
        [
          'Email/query',
          {
            accountId: jmapConfig.accountId,
            filter: {
              text: wildcardQuery, // Full-text search with wildcards
            },
            sort: [{ property: 'receivedAt', isAscending: false }],
            position: offset,
            limit: 200,
          },
          'query1',
        ],
      ]);
    } catch (textFilterError) {
      // If 'text' filter fails, fall back to OR conditions with wildcards
      logger.warn('Text filter failed, falling back to OR conditions', textFilterError);
      queryResponse = await jmapRequest(jmapConfig, [
        [
          'Email/query',
          {
            accountId: jmapConfig.accountId,
            filter: {
              operator: 'OR',
              conditions: [
                { from: wildcardQuery },
                { to: wildcardQuery },
                { subject: wildcardQuery },
              ],
            },
            sort: [{ property: 'receivedAt', isAscending: false }],
            position: offset,
            limit: 200,
          },
          'query1',
        ],
      ]);
    }

    const emailIds = queryResponse.methodResponses?.[0]?.[1]?.ids;

    if (!emailIds || emailIds.length === 0) {
      logger.info('No emails found matching search query');
      return [];
    }

    logger.info(`Found ${emailIds.length} emails matching query`);

    // Fetch email metadata
    const getResponse = await jmapRequest(jmapConfig, [
      [
        'Email/get',
        {
          accountId: jmapConfig.accountId,
          ids: emailIds,
          properties: ['from', 'to', 'subject', 'receivedAt', 'preview'],
        },
        'get1',
      ],
    ]);

    const emailsList = getResponse.methodResponses[0][1].list;

    // Get existing contacts for this email account
    const existingContacts = await db
      .select()
      .from(contacts)
      .where(eq(contacts.emailAccountId, emailAccountId));

    const contactEmailsSet = new Set(existingContacts.map((c) => c.email.toLowerCase()));

    // Group by sender (unique senders)
    const sendersMap = new Map();
    const myEmail = jmapConfig.username.toLowerCase();

    emailsList.forEach((email) => {
      const fromEmail = email.from?.[0]?.email;

      if (!fromEmail || fromEmail.toLowerCase() === myEmail) {
        return; // Skip my own emails
      }

      // Keep only the most recent email from each sender
      if (!sendersMap.has(fromEmail)) {
        sendersMap.set(fromEmail, {
          email: fromEmail,
          name: email.from?.[0]?.name || fromEmail.split('@')[0],
          latestSubject: email.subject || '(No Subject)',
          latestDate: email.receivedAt,
          snippet: email.preview || '',
          isContact: contactEmailsSet.has(fromEmail.toLowerCase()),
        });
      }
    });

    // Convert to array, sort by date, and apply limit
    const senders = Array.from(sendersMap.values())
      .sort((a, b) => new Date(b.latestDate) - new Date(a.latestDate))
      .slice(0, limit);

    logger.success(`Found ${senders.length} unique senders matching query`);
    return senders;
  } catch (error) {
    logger.error('Failed to search emails in JMAP', error);
    throw error;
  }
};

/**
 * Sync emails with a specific contact (bidirectional: both from and to)
 * ONLY syncs from Inbox and Sent mailboxes (no Archive, Trash, Drafts)
 * ONLY syncs recent emails (last 30 days by default)
 */
export const syncEmailsFromSender = async (
  jmapConfig,
  emailAccountId,
  contactId,
  senderEmail,
  options = {}
) => {
  const { limit = 50, daysBack = 30 } = options;

  try {
    logger.info(`Syncing emails with contact: ${senderEmail} for account ${emailAccountId}`);

    // Get Inbox and Sent mailboxes ONLY
    const mailboxes = await getMailboxes(jmapConfig);
    const inboxMailbox = mailboxes.find(m => m.role === 'inbox' || m.name === 'Inbox' || m.name === 'INBOX');
    const sentMailbox = mailboxes.find(m => m.role === 'sent' || m.name === 'Sent');

    if (!inboxMailbox) {
      logger.error('Inbox mailbox not found');
      throw new Error('Inbox mailbox not found');
    }

    logger.info(`Using mailboxes: Inbox (${inboxMailbox.id})${sentMailbox ? `, Sent (${sentMailbox.id})` : ''}`);

    // Calculate date threshold (only emails from last X days)
    const dateThreshold = new Date();
    dateThreshold.setDate(dateThreshold.getDate() - daysBack);
    const dateThresholdISO = dateThreshold.toISOString();

    logger.info(`Filtering: last ${daysBack} days, from Inbox/Sent only, for ${senderEmail}`);

    // Query emails FROM the contact
    const queryFromResponse = await jmapRequest(jmapConfig, [
      [
        'Email/query',
        {
          accountId: jmapConfig.accountId,
          filter: {
            operator: 'AND',
            conditions: [
              { inMailbox: inboxMailbox.id },
              { from: senderEmail },
              { after: dateThresholdISO }
            ]
          },
          sort: [{ property: 'receivedAt', isAscending: false }],
          limit,
        },
        'query1',
      ],
    ]);

    const fromEmailIds = queryFromResponse.methodResponses[0][1].ids || [];
    logger.info(`Found ${fromEmailIds.length} emails FROM ${senderEmail}`);

    // Query emails TO the contact (from Sent folder if it exists)
    let toEmailIds = [];
    if (sentMailbox) {
      const queryToResponse = await jmapRequest(jmapConfig, [
        [
          'Email/query',
          {
            accountId: jmapConfig.accountId,
            filter: {
              operator: 'AND',
              conditions: [
                { inMailbox: sentMailbox.id },
                { to: senderEmail },
                { after: dateThresholdISO }
              ]
            },
            sort: [{ property: 'receivedAt', isAscending: false }],
            limit,
          },
          'query2',
        ],
      ]);

      toEmailIds = queryToResponse.methodResponses[0][1].ids || [];
      logger.info(`Found ${toEmailIds.length} emails TO ${senderEmail}`);
    }

    // Combine and deduplicate
    const emailIds = [...new Set([...fromEmailIds, ...toEmailIds])];
    logger.info(`Total unique emails: ${emailIds.length}`);

    if (emailIds.length === 0) {
      return { total: 0, saved: 0 };
    }

    // Fetch full email details
    const getResponse = await jmapRequest(jmapConfig, [
      [
        'Email/get',
        {
          accountId: jmapConfig.accountId,
          ids: emailIds,
          properties: [
            'id',
            'messageId',
            'threadId',
            'inReplyTo',
            'from',
            'to',
            'subject',
            'receivedAt',
            'textBody',
            'htmlBody',
            'bodyValues',
            'keywords',
          ],
          fetchTextBodyValues: true,
          fetchHTMLBodyValues: true,
        },
        'get1',
      ],
    ]);

    const emailsList = getResponse.methodResponses[0][1].list;
    let savedCount = 0;

    // Save emails to database
    for (const email of emailsList) {
      try {
        const fromEmail = email.from?.[0]?.email;
        const toEmail = email.to?.[0]?.email;
        const messageId = Array.isArray(email.messageId) ? email.messageId[0] : email.messageId;
        const inReplyTo = Array.isArray(email.inReplyTo) ? email.inReplyTo[0] : email.inReplyTo;
        const isRead = email.keywords && email.keywords['$seen'] === true;

        // Skip emails without from or to (malformed data)
        if (!fromEmail && !toEmail) {
          logger.warn(`Skipping email ${messageId} - missing both from and to fields`);
          continue;
        }

        // VALIDATION: Email must belong to this contact
        // Email belongs to contact if:
        // - from === senderEmail (received email FROM contact)
        // - to === senderEmail (sent email TO contact)
        const belongsToContact =
          fromEmail?.toLowerCase() === senderEmail.toLowerCase() ||
          toEmail?.toLowerCase() === senderEmail.toLowerCase();

        if (!belongsToContact) {
          logger.warn(`Skipping email ${messageId} - does not belong to contact ${senderEmail} (from: ${fromEmail}, to: ${toEmail})`);
          continue;
        }

        // Skip if already exists
        const [existing] = await db
          .select()
          .from(emails)
          .where(eq(emails.messageId, messageId))
          .limit(1);

        if (existing) {
          continue;
        }

        // Save email
        await db.insert(emails).values({
          emailAccountId,
          contactId,
          jmapId: email.id,
          messageId,
          threadId: email.threadId || messageId,
          inReplyTo: inReplyTo || null,
          from: fromEmail,
          to: toEmail,
          subject: email.subject || '(No Subject)',
          body:
            email.bodyValues?.[email.textBody?.[0]?.partId]?.value ||
            email.bodyValues?.[email.htmlBody?.[0]?.partId]?.value ||
            '(Empty message)',
          date: email.receivedAt ? new Date(email.receivedAt) : new Date(),
          isRead,
          sentByUserId: null, // Prijatý email, nie odpoveď
        });

        savedCount++;
      } catch (error) {
        logger.error(`Error saving email ${email.messageId}`, error);
      }
    }

    logger.success(`Synced ${savedCount} new emails with ${senderEmail}`);
    return { total: emailsList.length, saved: savedCount };
  } catch (error) {
    logger.error('Failed to sync emails with contact', error);
    throw error;
  }
};

/**
 * Mark email as read/unread in JMAP and local DB
 */
export const markEmailAsRead = async (jmapConfig, userId, jmapId, isRead) => {
  try {
    logger.info(`Marking email ${jmapId} as ${isRead ? 'read' : 'unread'}`);

    // Get current keywords
    const getResponse = await jmapRequest(jmapConfig, [
      [
        'Email/get',
        {
          accountId: jmapConfig.accountId,
          ids: [jmapId],
          properties: ['keywords', 'mailboxIds'],
        },
        'get1',
      ],
    ]);

    const email = getResponse.methodResponses[0][1].list?.[0];

    if (!email) {
      // Update local DB even if JMAP fails
      await db.update(emails).set({ isRead }).where(eq(emails.jmapId, jmapId));
      throw new Error('Email not found in JMAP');
    }

    // Build new keywords
    const newKeywords = { ...email.keywords };
    if (isRead) {
      newKeywords['$seen'] = true;
    } else {
      delete newKeywords['$seen'];
    }

    // Update in JMAP
    const updateResponse = await jmapRequest(jmapConfig, [
      [
        'Email/set',
        {
          accountId: jmapConfig.accountId,
          update: {
            [jmapId]: {
              keywords: newKeywords,
            },
          },
        },
        'set1',
      ],
    ]);

    const updateResult = updateResponse.methodResponses[0][1];

    // Check if update succeeded
    if (updateResult.notUpdated?.[jmapId]) {
      logger.error('Failed to update email in JMAP', updateResult.notUpdated[jmapId]);
    }

    // Update in local DB
    await db.update(emails).set({ isRead }).where(eq(emails.jmapId, jmapId));

    logger.success(`Email ${jmapId} marked as ${isRead ? 'read' : 'unread'}`);
    return { success: true };
  } catch (error) {
    logger.error('Error marking email as read', error);

    // Still try to update local DB
    try {
      await db.update(emails).set({ isRead }).where(eq(emails.jmapId, jmapId));
    } catch (dbError) {
      logger.error('Failed to update DB', dbError);
    }

    throw error;
  }
};

/**
 * Send email via JMAP
 */
export const sendEmail = async (jmapConfig, userId, emailAccountId, to, subject, body, inReplyTo = null, threadId = null) => {
  try {
    logger.info(`Sending email to: ${to}`);

    // Get mailboxes
    const mailboxes = await getMailboxes(jmapConfig);
    const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');

    if (!sentMailbox) {
      throw new Error('Sent mailbox not found');
    }

    // Create email draft
    const createResponse = await jmapRequest(jmapConfig, [
      [
        'Email/set',
        {
          accountId: jmapConfig.accountId,
          create: {
            draft: {
              mailboxIds: {
                [sentMailbox.id]: true,
              },
              keywords: {
                $draft: true,
              },
              from: [{ email: jmapConfig.username }],
              to: [{ email: to }],
              subject: subject,
              textBody: [{ partId: 'body', type: 'text/plain' }],
              bodyValues: {
                body: {
                  value: body,
                },
              },
              inReplyTo: inReplyTo ? [inReplyTo] : null,
              references: inReplyTo ? [inReplyTo] : null,
            },
          },
        },
        'set1',
      ],
    ]);

    const createdEmailId = createResponse.methodResponses[0][1].created?.draft?.id;

    if (!createdEmailId) {
      throw new Error('Failed to create email draft');
    }

    // Get user identity
    const identities = await getIdentities(jmapConfig);
    const identity = identities.find((i) => i.email === jmapConfig.username) || identities[0];

    if (!identity) {
      throw new Error('No identity found for sending email');
    }

    // Submit the email
    const submitResponse = await jmapRequest(jmapConfig, [
      [
        'EmailSubmission/set',
        {
          accountId: jmapConfig.accountId,
          create: {
            submission: {
              emailId: createdEmailId,
              identityId: identity.id,
            },
          },
        },
        'submit1',
      ],
    ]);

    const submissionId = submitResponse.methodResponses[0][1].created?.submission?.id;

    if (!submissionId) {
      throw new Error('Failed to submit email');
    }

    logger.success(`Email sent successfully to ${to}`);

    // Find contact by recipient email address to properly link the sent email
    const [recipientContact] = await db
      .select()
      .from(contacts)
      .where(
        and(
          eq(contacts.emailAccountId, emailAccountId),
          eq(contacts.email, to)
        )
      )
      .limit(1);

    // Save sent email to database
    const messageId = `<${Date.now()}.${Math.random().toString(36).substr(2, 9)}@${jmapConfig.username.split('@')[1]}>`;

    await db.insert(emails).values({
      emailAccountId,
      contactId: recipientContact?.id || null, // Link to contact if recipient is in contacts
      jmapId: createdEmailId,
      messageId,
      threadId: threadId || messageId,
      inReplyTo: inReplyTo || null,
      from: jmapConfig.username,
      to,
      subject,
      body,
      date: new Date(),
      isRead: true, // Sent emails are always read
      sentByUserId: userId, // Track who sent this email
    });

    logger.info(`Sent email linked to contact: ${recipientContact ? recipientContact.id : 'none (not in contacts)'}`);

    return { success: true, messageId };
  } catch (error) {
    logger.error('Error sending email', error);
    throw error;
  }
};
