import { eq, and, sql } from 'drizzle-orm';
import { db } from '../config/database.js';
import { emailAccounts, userEmailAccounts, users } from '../db/schema.js';
import { encryptPassword, decryptPassword } from '../utils/password.js';
import { validateJmapCredentials } from './email.service.js';
import {
  NotFoundError,
  ValidationError,
  ConflictError,
  AuthenticationError,
} from '../utils/errors.js';
import { logger } from '../utils/logger.js';

/**
 * Get all email accounts for a user (cez many-to-many)
 */
export const getUserEmailAccounts = async (userId) => {
  const accounts = await db
    .select({
      id: emailAccounts.id,
      email: emailAccounts.email,
      jmapAccountId: emailAccounts.jmapAccountId,
      isActive: emailAccounts.isActive,
      isPrimary: userEmailAccounts.isPrimary,
      addedAt: userEmailAccounts.addedAt,
      createdAt: emailAccounts.createdAt,
    })
    .from(userEmailAccounts)
    .innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
    .where(eq(userEmailAccounts.userId, userId))
    .orderBy(userEmailAccounts.isPrimary, emailAccounts.createdAt);

  return accounts;
};

/**
 * Get a specific email account by ID (check user has access)
 */
export const getEmailAccountById = async (accountId, userId) => {
  const [link] = await db
    .select()
    .from(userEmailAccounts)
    .where(
      and(
        eq(userEmailAccounts.emailAccountId, accountId),
        eq(userEmailAccounts.userId, userId)
      )
    )
    .limit(1);

  if (!link) {
    throw new NotFoundError('Email účet nenájdený alebo nemáte k nemu prístup');
  }

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

  if (!account) {
    throw new NotFoundError('Email účet nenájdený');
  }

  return account;
};

/**
 * Get user's primary email account
 */
export const getPrimaryEmailAccount = async (userId) => {
  const [result] = await db
    .select({
      id: emailAccounts.id,
      email: emailAccounts.email,
      jmapAccountId: emailAccounts.jmapAccountId,
      isActive: emailAccounts.isActive,
    })
    .from(userEmailAccounts)
    .innerJoin(emailAccounts, eq(userEmailAccounts.emailAccountId, emailAccounts.id))
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.isPrimary, true)
      )
    )
    .limit(1);

  return result || null;
};

/**
 * Create a new email account with JMAP validation
 * Automatic many-to-many link vytvorí pre používateľa, ktorý účet vytvoril
 */
export const createEmailAccount = async (userId, email, emailPassword) => {
  // Check if email account already exists (globally)
  const [existing] = await db
    .select()
    .from(emailAccounts)
    .where(eq(emailAccounts.email, email))
    .limit(1);

  if (existing) {
    // Email account už existuje - skontroluj či user už má prístup
    const [userLink] = await db
      .select()
      .from(userEmailAccounts)
      .where(
        and(
          eq(userEmailAccounts.userId, userId),
          eq(userEmailAccounts.emailAccountId, existing.id)
        )
      )
      .limit(1);

    if (userLink) {
      throw new ConflictError('Tento email účet už máte pripojený');
    }

    // Email account existuje, ale user ho nemá - môžeme ho zdieľať
    // Ale najprv overíme heslo
    try {
      await validateJmapCredentials(email, emailPassword);
    } catch (error) {
      throw new AuthenticationError('Nesprávne heslo k existujúcemu email účtu');
    }

    // Check if this is the first email account for this user
    const existingAccounts = await getUserEmailAccounts(userId);
    const isFirst = existingAccounts.length === 0;

    // Link user k existujúcemu accountu
    await db.insert(userEmailAccounts).values({
      userId,
      emailAccountId: existing.id,
      isPrimary: isFirst,
    });

    return {
      id: existing.id,
      email: existing.email,
      jmapAccountId: existing.jmapAccountId,
      isPrimary: isFirst,
      isActive: existing.isActive,
      shared: true, // Indikuje že ide o zdieľaný account
    };
  }

  // Email account neexistuje - vytvoríme nový
  // Validate JMAP credentials and get account ID
  let jmapAccountId;
  try {
    const validation = await validateJmapCredentials(email, emailPassword);
    jmapAccountId = validation.accountId;
  } catch (error) {
    throw new AuthenticationError(
      'Nepodarilo sa pripojiť k emailovému účtu. Skontrolujte prihlasovacie údaje.'
    );
  }

  // Encrypt password
  const encryptedPassword = encryptPassword(emailPassword);

  // Check if this is the first email account for this user
  const existingAccounts = await getUserEmailAccounts(userId);
  const isFirst = existingAccounts.length === 0;

  // Use transaction to create email account and link it to user
  const result = await db.transaction(async (tx) => {
    // Create email account
    const [newAccount] = await tx
      .insert(emailAccounts)
      .values({
        email,
        emailPassword: encryptedPassword,
        jmapAccountId,
        isActive: true,
      })
      .returning();

    // Link user to email account
    await tx.insert(userEmailAccounts).values({
      userId,
      emailAccountId: newAccount.id,
      isPrimary: isFirst, // First account is automatically primary
    });

    return newAccount;
  });

  return {
    id: result.id,
    email: result.email,
    jmapAccountId: result.jmapAccountId,
    isPrimary: isFirst,
    isActive: result.isActive,
    shared: false,
  };
};

/**
 * Update email account password
 */
export const updateEmailAccountPassword = async (accountId, userId, newPassword) => {
  // Check user has access
  await getEmailAccountById(accountId, userId);

  // Validate new JMAP credentials
  const [account] = await db
    .select()
    .from(emailAccounts)
    .where(eq(emailAccounts.id, accountId))
    .limit(1);

  try {
    await validateJmapCredentials(account.email, newPassword);
  } catch (error) {
    throw new AuthenticationError(
      'Nepodarilo sa overiť nové heslo. Skontrolujte prihlasovacie údaje.'
    );
  }

  // Encrypt password
  const encryptedPassword = encryptPassword(newPassword);

  // Update password
  const [updated] = await db
    .update(emailAccounts)
    .set({
      emailPassword: encryptedPassword,
      updatedAt: new Date(),
    })
    .where(eq(emailAccounts.id, accountId))
    .returning();

  return {
    id: updated.id,
    email: updated.email,
    updatedAt: updated.updatedAt,
  };
};

/**
 * Toggle email account active status
 */
export const toggleEmailAccountStatus = async (accountId, userId, isActive) => {
  // Check user has access
  await getEmailAccountById(accountId, userId);

  // Get user's link to check if it's primary
  const [link] = await db
    .select()
    .from(userEmailAccounts)
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.emailAccountId, accountId)
      )
    )
    .limit(1);

  // Cannot deactivate primary account
  if (link.isPrimary && !isActive) {
    throw new ValidationError('Nemôžete deaktivovať primárny email účet');
  }

  const [updated] = await db
    .update(emailAccounts)
    .set({
      isActive,
      updatedAt: new Date(),
    })
    .where(eq(emailAccounts.id, accountId))
    .returning();

  return {
    id: updated.id,
    isActive: updated.isActive,
  };
};

/**
 * Set an email account as primary FOR SPECIFIC USER
 */
export const setPrimaryEmailAccount = async (accountId, userId) => {
  // Check user has access
  await getEmailAccountById(accountId, userId);

  // Use transaction to prevent race conditions
  await db.transaction(async (tx) => {
    // Remove primary flag from all user's accounts
    await tx
      .update(userEmailAccounts)
      .set({ isPrimary: false })
      .where(eq(userEmailAccounts.userId, userId));

    // Set new primary account
    await tx
      .update(userEmailAccounts)
      .set({ isPrimary: true })
      .where(
        and(
          eq(userEmailAccounts.userId, userId),
          eq(userEmailAccounts.emailAccountId, accountId)
        )
      );

    // Make sure email account is active
    await tx
      .update(emailAccounts)
      .set({
        isActive: true,
        updatedAt: new Date(),
      })
      .where(eq(emailAccounts.id, accountId));
  });

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

  return {
    id: account.id,
    email: account.email,
    isPrimary: true,
  };
};

/**
 * Remove user's access to email account
 * Ak je to posledný používateľ, vymaže aj samotný email account
 */
export const removeUserFromEmailAccount = async (accountId, userId) => {
  // Check user has access
  await getEmailAccountById(accountId, userId);

  // Get user's link
  const [link] = await db
    .select()
    .from(userEmailAccounts)
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.emailAccountId, accountId)
      )
    )
    .limit(1);

  // If this is user's primary account, make sure they have another one to set as primary
  if (link.isPrimary) {
    const allUserAccounts = await getUserEmailAccounts(userId);
    if (allUserAccounts.length === 1) {
      throw new ValidationError('Nemôžete odstrániť posledný email účet');
    }

    // Set another account as primary
    const otherAccount = allUserAccounts.find(acc => acc.id !== accountId);
    if (otherAccount) {
      await db
        .update(userEmailAccounts)
        .set({ isPrimary: true })
        .where(
          and(
            eq(userEmailAccounts.userId, userId),
            eq(userEmailAccounts.emailAccountId, otherAccount.id)
          )
        );
    }
  }

  // Remove user's link
  await db
    .delete(userEmailAccounts)
    .where(
      and(
        eq(userEmailAccounts.userId, userId),
        eq(userEmailAccounts.emailAccountId, accountId)
      )
    );

  // Check if this was the last user with access to this email account
  const [remainingLinks] = await db
    .select({ count: sql`count(*)::int` })
    .from(userEmailAccounts)
    .where(eq(userEmailAccounts.emailAccountId, accountId));

  // If no users have access anymore, delete the email account itself
  if (remainingLinks.count === 0) {
    await db
      .delete(emailAccounts)
      .where(eq(emailAccounts.id, accountId));

    return {
      message: 'Email účet bol úspešne odstránený (posledný používateľ)',
      deletedAccountId: accountId,
    };
  }

  return {
    message: 'Prístup k email účtu bol odobraný',
    deletedAccountId: accountId,
  };
};

/**
 * Get email account with decrypted password (for JMAP operations)
 */
export const getEmailAccountWithCredentials = async (accountId, userId) => {
  const account = await getEmailAccountById(accountId, userId);
  const decryptedPassword = decryptPassword(account.emailPassword);

  return {
    id: account.id,
    email: account.email,
    emailPassword: decryptedPassword,
    jmapAccountId: account.jmapAccountId,
    isActive: account.isActive,
  };
};

/**
 * Get all users with access to an email account
 */
export const getUsersWithAccessToAccount = async (accountId) => {
  const usersWithAccess = await db
    .select({
      userId: users.id,
      username: users.username,
      firstName: users.firstName,
      lastName: users.lastName,
      isPrimary: userEmailAccounts.isPrimary,
      addedAt: userEmailAccounts.addedAt,
    })
    .from(userEmailAccounts)
    .innerJoin(users, eq(userEmailAccounts.userId, users.id))
    .where(eq(userEmailAccounts.emailAccountId, accountId));

  return usersWithAccess;
};

/**
 * Share email account with another user
 */
export const shareEmailAccountWithUser = async (accountId, ownerId, targetUserId) => {
  // Check owner has access
  await getEmailAccountById(accountId, ownerId);

  // Check target user exists
  const [targetUser] = await db
    .select()
    .from(users)
    .where(eq(users.id, targetUserId))
    .limit(1);

  if (!targetUser) {
    throw new NotFoundError('Cieľový používateľ nenájdený');
  }

  // Check if target user already has access
  const [existingLink] = await db
    .select()
    .from(userEmailAccounts)
    .where(
      and(
        eq(userEmailAccounts.userId, targetUserId),
        eq(userEmailAccounts.emailAccountId, accountId)
      )
    )
    .limit(1);

  if (existingLink) {
    throw new ConflictError('Používateľ už má prístup k tomuto email účtu');
  }

  // Check if this is the first email account for target user
  const targetUserAccounts = await getUserEmailAccounts(targetUserId);
  const isFirst = targetUserAccounts.length === 0;

  // Create link
  await db.insert(userEmailAccounts).values({
    userId: targetUserId,
    emailAccountId: accountId,
    isPrimary: isFirst,
  });

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

  return {
    success: true,
    message: `Email účet ${account.email} bol zdieľaný s používateľom ${targetUser.username}`,
    sharedWith: {
      userId: targetUser.id,
      username: targetUser.username,
      firstName: targetUser.firstName,
      lastName: targetUser.lastName,
    },
  };
};
