import { eq, and, gte, lt } from 'drizzle-orm';
import { db } from '../../config/database.js';
import { events, eventUsers, users, emailAccounts, userEmailAccounts } from '../../db/schema.js';
import { decryptPassword } from '../../utils/password.js';
import { jmapRequest, getMailboxes, getIdentities } from '../../services/jmap/client.js';
import { logger } from '../../utils/logger.js';
import {
  generateEventNotificationHtml,
  generateEventNotificationText,
  generateEventNotificationSubject,
} from './email-template.js';

// --- Private helpers ---

const getSenderAccount = async () => {
  const senderEmail = process.env.NOTIFICATION_SENDER_EMAIL;

  if (!senderEmail) {
    logger.error('NOTIFICATION_SENDER_EMAIL nie je nastavený v .env');
    return null;
  }

  const [account] = await db
    .select()
    .from(emailAccounts)
    .where(eq(emailAccounts.email, senderEmail))
    .limit(1);

  if (!account) {
    logger.error(`Email účet ${senderEmail} nebol nájdený v databáze`);
    return null;
  }

  try {
    const decryptedPassword = decryptPassword(account.emailPassword);
    return {
      id: account.id,
      email: account.email,
      password: decryptedPassword,
      jmapAccountId: account.jmapAccountId,
    };
  } catch (error) {
    logger.error(`Nepodarilo sa dešifrovať heslo pre ${senderEmail}`, error);
    return null;
  }
};

const getSenderAccountByUserId = async (userId) => {
  const [result] = await db
    .select({
      id: emailAccounts.id,
      email: emailAccounts.email,
      emailPassword: emailAccounts.emailPassword,
      jmapAccountId: emailAccounts.jmapAccountId,
    })
    .from(userEmailAccounts)
    .innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.isPrimary, true)
      )
    )
    .limit(1);

  if (!result) {
    logger.error(`Používateľ ${userId} nemá nastavený primárny email účet`);
    return null;
  }

  try {
    const decryptedPassword = decryptPassword(result.emailPassword);
    return {
      id: result.id,
      email: result.email,
      password: decryptedPassword,
      jmapAccountId: result.jmapAccountId,
    };
  } catch (error) {
    logger.error(`Nepodarilo sa dešifrovať heslo pre ${result.email}`, error);
    return null;
  }
};

const getEventsInRange = async (start, end) => {
  logger.info(`Hľadám udalosti od ${start.toISOString()} do ${end.toISOString()}`);

  return await db
    .select({
      eventId: events.id,
      title: events.title,
      description: events.description,
      type: events.type,
      start: events.start,
      end: events.end,
      userId: users.id,
      username: users.username,
      firstName: users.firstName,
      lastName: users.lastName,
    })
    .from(events)
    .innerJoin(eventUsers, eq(events.id, eventUsers.eventId))
    .innerJoin(users, eq(eventUsers.userId, users.id))
    .where(
      and(
        gte(events.start, start),
        lt(events.start, end)
      )
    );
};

const groupEventsByUser = (eventRows) => {
  const userNotifications = new Map();

  for (const row of eventRows) {
    const key = `${row.userId}-${row.eventId}`;
    if (!userNotifications.has(key)) {
      userNotifications.set(key, {
        userId: row.userId,
        username: row.username,
        firstName: row.firstName,
        lastName: row.lastName,
        event: {
          id: row.eventId,
          title: row.title,
          description: row.description,
          type: row.type,
          start: row.start,
          end: row.end,
        },
      });
    }
  }

  return userNotifications;
};

const getUserEmail = async (userId) => {
  const [result] = await db
    .select({
      email: emailAccounts.email,
    })
    .from(userEmailAccounts)
    .innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.isPrimary, true)
      )
    )
    .limit(1);

  return result?.email || null;
};

const sendNotificationEmail = async (jmapConfig, to, subject, htmlBody, textBody) => {
  try {
    const mailboxes = await getMailboxes(jmapConfig);
    const sentMailbox = mailboxes.find((m) => m.role === 'sent' || m.name === 'Sent');

    if (!sentMailbox) {
      logger.error('Priečinok Odoslané nebol nájdený');
      return false;
    }

    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,
              htmlBody: [{ partId: 'html', type: 'text/html' }],
              textBody: [{ partId: 'text', type: 'text/plain' }],
              bodyValues: {
                html: { value: htmlBody },
                text: { value: textBody },
              },
            },
          },
        },
        'set1',
      ],
    ]);

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

    if (!createdEmailId) {
      logger.error('Nepodarilo sa vytvoriť koncept emailu', createResponse.methodResponses[0][1].notCreated);
      return false;
    }

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

    if (!identity) {
      logger.error('Nenašla sa identita pre odosielanie emailov');
      return false;
    }

    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) {
      logger.error('Nepodarilo sa odoslať email', submitResponse.methodResponses[0][1].notCreated);
      return false;
    }

    return true;
  } catch (error) {
    logger.error(`Chyba pri odosielaní emailu na ${to}`, error);
    return false;
  }
};

const sendNotificationsToUsers = async (jmapConfig, userNotifications, { getSubject, logPrefix }) => {
  const stats = { sent: 0, failed: 0, skipped: 0 };

  for (const [, data] of userNotifications) {
    const { userId, username, firstName, event } = data;

    const userEmail = await getUserEmail(userId);

    if (!userEmail) {
      stats.skipped++;
      continue;
    }

    const subject = getSubject(event);
    const htmlBody = generateEventNotificationHtml({ firstName, username, event });
    const textBody = generateEventNotificationText({ firstName, username, event });

    logger.info(`Odosielam ${logPrefix}notifikáciu pre ${username} - udalosť: ${event.title}`);

    const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);

    if (success) {
      logger.success(`Email úspešne odoslaný na ${userEmail}`);
      stats.sent++;
    } else {
      logger.error(`Nepodarilo sa odoslať email na ${userEmail}`);
      stats.failed++;
    }
  }

  return stats;
};

const buildJmapConfig = (senderAccount) => ({
  server: process.env.JMAP_SERVER,
  username: senderAccount.email,
  password: senderAccount.password,
  accountId: senderAccount.jmapAccountId,
});

// --- Public API ---

/**
 * Send notification for a single event to all assigned users
 */
export const sendSingleEventNotification = async (eventId, adminUserId) => {
  logger.info(`=== Odosielam notifikácie pre event ${eventId} od admina ${adminUserId} ===`);

  const senderAccount = await getSenderAccountByUserId(adminUserId);
  if (!senderAccount) {
    throw new Error('Nemáte nastavený primárny email účet. Nastavte ho v profile.');
  }

  const jmapConfig = buildJmapConfig(senderAccount);
  logger.info(`Odosielam z účtu: ${senderAccount.email}`);

  const eventData = await db
    .select({
      eventId: events.id,
      title: events.title,
      description: events.description,
      type: events.type,
      start: events.start,
      end: events.end,
      userId: users.id,
      username: users.username,
      firstName: users.firstName,
      lastName: users.lastName,
    })
    .from(events)
    .innerJoin(eventUsers, eq(events.id, eventUsers.eventId))
    .innerJoin(users, eq(eventUsers.userId, users.id))
    .where(eq(events.id, eventId));

  if (eventData.length === 0) {
    throw new Error('Event nebol nájdený alebo nemá priradených používateľov');
  }

  const eventTitle = eventData[0].title;
  logger.info(`Event: ${eventTitle}, priradených používateľov: ${eventData.length}`);

  const userNotifications = groupEventsByUser(eventData);

  const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
    getSubject: generateEventNotificationSubject,
    logPrefix: '',
  });

  logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);

  return { ...stats, eventTitle };
};

/**
 * Send notifications for tomorrow's events
 */
export const sendEventNotifications = async () => {
  logger.info('=== Spúšťam kontrolu zajtrajších udalostí ===');

  const senderAccount = await getSenderAccount();
  if (!senderAccount) {
    logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
    return { sent: 0, failed: 0, skipped: 0 };
  }

  const jmapConfig = buildJmapConfig(senderAccount);

  const now = new Date();
  const startOfTomorrow = new Date(now);
  startOfTomorrow.setDate(startOfTomorrow.getDate() + 1);
  startOfTomorrow.setHours(0, 0, 0, 0);
  const endOfTomorrow = new Date(startOfTomorrow);
  endOfTomorrow.setHours(23, 59, 59, 999);

  const tomorrowEvents = await getEventsInRange(startOfTomorrow, endOfTomorrow);

  if (tomorrowEvents.length === 0) {
    logger.info('Žiadne udalosti na zajtra');
    return { sent: 0, failed: 0, skipped: 0 };
  }

  logger.info(`Nájdených ${tomorrowEvents.length} priradení udalostí na zajtra`);

  const userNotifications = groupEventsByUser(tomorrowEvents);
  logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);

  const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
    getSubject: generateEventNotificationSubject,
    logPrefix: '',
  });

  logger.info(`=== Hotovo: odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);

  return stats;
};

/**
 * Send notifications for events starting in the next hour
 */
export const sendOneHourBeforeNotifications = async () => {
  logger.info('=== Spúšťam kontrolu udalostí začínajúcich v najbližšej hodine ===');

  const senderAccount = await getSenderAccount();
  if (!senderAccount) {
    logger.error('Nemôžem pokračovať bez platného odosielacieho účtu');
    return { sent: 0, failed: 0, skipped: 0 };
  }

  const jmapConfig = buildJmapConfig(senderAccount);

  const now = new Date();
  const endOfRange = new Date(now);
  endOfRange.setHours(endOfRange.getHours() + 1);

  const upcomingEvents = await getEventsInRange(now, endOfRange);

  if (upcomingEvents.length === 0) {
    logger.info('Žiadne udalosti v najbližšej hodine');
    return { sent: 0, failed: 0, skipped: 0 };
  }

  logger.info(`Nájdených ${upcomingEvents.length} priradení udalostí v najbližšej hodine`);

  const userNotifications = groupEventsByUser(upcomingEvents);
  logger.info(`Unikátnych notifikácií na odoslanie: ${userNotifications.size}`);

  const stats = await sendNotificationsToUsers(jmapConfig, userNotifications, {
    getSubject: (event) => {
      const typeLabel = event.type === 'meeting' ? 'Stretnutie' : (event.type === 'important' ? 'Dôležité' : 'Udalosť');
      return `Pripomienka: ${typeLabel} - ${event.title} (o 1 hodinu)`;
    },
    logPrefix: '1h ',
  });

  logger.info(`=== Hotovo (1h notifikácie): odoslaných ${stats.sent}, neúspešných ${stats.failed}, preskočených ${stats.skipped} ===`);

  return stats;
};
