import { db } from '../config/database.js';
import { contacts, emails, companies, emailAccounts } from '../db/schema.js';
import { eq, and, desc, or, ne } from 'drizzle-orm';
import { NotFoundError, ConflictError } from '../utils/errors.js';
import { syncEmailsFromSender } from './jmap/index.js';
import { logContactLinkedToCompany, logCompanyCreatedFromContact } from './audit.service.js';

/**
 * Get contacts with related data (emailAccount, company) using joins
 * Avoids N+1 query problem by fetching all related data in a single query
 */
export const getContactsWithRelations = async (emailAccountId) => {
  const result = await db
    .select({
      contact: contacts,
      emailAccount: emailAccounts,
      company: companies,
    })
    .from(contacts)
    .leftJoin(emailAccounts, eq(contacts.emailAccountId, emailAccounts.id))
    .leftJoin(companies, eq(contacts.companyId, companies.id))
    .where(eq(contacts.emailAccountId, emailAccountId))
    .orderBy(desc(contacts.addedAt));

  return result.map((row) => ({
    ...row.contact,
    emailAccount: row.emailAccount,
    company: row.company,
  }));
};

/**
 * Get all contacts for an email account
 * Kontakty patria k email accountu, nie k jednotlivým používateľom
 */
export const getContactsForEmailAccount = async (emailAccountId) => {
  const accountContacts = await db
    .select()
    .from(contacts)
    .where(eq(contacts.emailAccountId, emailAccountId))
    .orderBy(desc(contacts.addedAt));

  return accountContacts;
};

/**
 * Add a new contact to an email account
 * Uses transaction for atomic operations
 */
export const addContact = async (emailAccountId, jmapConfig, email, name = '', notes = '', addedByUserId = null) => {
  // Check if contact already exists for this email account
  const [existing] = await db
    .select()
    .from(contacts)
    .where(
      and(
        eq(contacts.emailAccountId, emailAccountId),
        eq(contacts.email, email)
      )
    )
    .limit(1);

  if (existing) {
    throw new ConflictError('Kontakt už existuje');
  }

  // Use transaction for atomic contact creation and email reassignment
  const newContact = await db.transaction(async (tx) => {
    // Create contact
    const [contact] = await tx
      .insert(contacts)
      .values({
        emailAccountId,
        email,
        name: name || email.split('@')[0],
        notes: notes || null,
        addedBy: addedByUserId,
      })
      .returning();

    // REASSIGN: Fix any existing emails that belong to this contact but have wrong contactId
    // Find emails where:
    // - from === contact.email OR to === contact.email
    // - contactId !== contact.id (belongs to different contact)
    // - emailAccountId matches
    const emailsToReassign = await tx
      .select()
      .from(emails)
      .where(
        and(
          eq(emails.emailAccountId, emailAccountId),
          ne(emails.contactId, contact.id),
          or(
            eq(emails.from, email),
            eq(emails.to, email)
          )
        )
      );

    if (emailsToReassign.length > 0) {
      // Bulk update contactId for these emails
      await tx
        .update(emails)
        .set({
          contactId: contact.id,
          companyId: contact.companyId || null,
        })
        .where(
          and(
            eq(emails.emailAccountId, emailAccountId),
            ne(emails.contactId, contact.id),
            or(
              eq(emails.from, email),
              eq(emails.to, email)
            )
          )
        );
    }

    return contact;
  });

  // Sync emails from this sender (outside transaction - external API call)
  try {
    await syncEmailsFromSender(jmapConfig, emailAccountId, newContact.id, email);
  } catch {
    // Don't throw - contact was created successfully
  }

  return newContact;
};

/**
 * Get a contact by ID (check it belongs to the email account)
 */
export const getContactById = async (contactId, emailAccountId) => {
  const [contact] = await db
    .select()
    .from(contacts)
    .where(
      and(
        eq(contacts.id, contactId),
        eq(contacts.emailAccountId, emailAccountId)
      )
    )
    .limit(1);

  if (!contact) {
    throw new NotFoundError('Kontakt nenájdený');
  }

  return contact;
};

/**
 * Remove a contact
 */
export const removeContact = async (contactId, emailAccountId) => {
  const contact = await getContactById(contactId, emailAccountId);

  // Delete contact (emails will be cascade deleted)
  await db.delete(contacts).where(eq(contacts.id, contactId));

  return { success: true, message: 'Kontakt bol odstránený' };
};

/**
 * Update contact
 */
export const updateContact = async (contactId, emailAccountId, { name, notes }) => {
  const contact = await getContactById(contactId, emailAccountId);

  const [updated] = await db
    .update(contacts)
    .set({
      name: name !== undefined ? name : contact.name,
      notes: notes !== undefined ? notes : contact.notes,
      updatedAt: new Date(),
    })
    .where(eq(contacts.id, contactId))
    .returning();

  return updated;
};

/**
 * Link company to contact
 */
export const linkCompanyToContact = async (contactId, emailAccountId, companyId, auditContext = null) => {
  const contact = await getContactById(contactId, emailAccountId);

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

  // Propagate company assignment to existing emails for this contact
  await db
    .update(emails)
    .set({
      companyId,
      updatedAt: new Date(),
    })
    .where(eq(emails.contactId, contactId));

  if (auditContext) {
    await logContactLinkedToCompany(auditContext.userId, contactId, companyId, updated.company?.name || companyId, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Unlink company from contact
 */
export const unlinkCompanyFromContact = async (contactId, emailAccountId) => {
  const contact = await getContactById(contactId, emailAccountId);

  const [updated] = await db
    .update(contacts)
    .set({
      companyId: null,
      updatedAt: new Date(),
    })
    .where(eq(contacts.id, contactId))
    .returning();

  // Remove company assignment from existing emails for this contact
  await db
    .update(emails)
    .set({
      companyId: null,
      updatedAt: new Date(),
    })
    .where(eq(emails.contactId, contactId));

  return updated;
};

/**
 * Create company from contact
 * Creates a new company using contact's information and links it
 */
export const createCompanyFromContact = async (contactId, emailAccountId, userId, companyData = {}, auditContext = null) => {
  const contact = await getContactById(contactId, emailAccountId);

  // Check if company with same name already exists
  if (companyData.name) {
    const [existing] = await db
      .select()
      .from(companies)
      .where(eq(companies.name, companyData.name))
      .limit(1);

    if (existing) {
      throw new ConflictError('Firma s týmto názvom už existuje');
    }
  }

  // Create company with contact's data as defaults
  const [newCompany] = await db
    .insert(companies)
    .values({
      name: companyData.name || contact.name || contact.email.split('@')[0],
      email: companyData.email || contact.email,
      phone: companyData.phone || null,
      address: companyData.address || null,
      city: companyData.city || null,
      country: companyData.country || null,
      website: companyData.website || null,
      description: companyData.description || null,
      createdBy: userId,
    })
    .returning();

  // Link contact to newly created company
  const [updatedContact] = await db
    .update(contacts)
    .set({
      companyId: newCompany.id,
      updatedAt: new Date(),
    })
    .where(eq(contacts.id, contactId))
    .returning();

  // Update existing emails for this contact to reference the new company
  await db
    .update(emails)
    .set({
      companyId: newCompany.id,
      updatedAt: new Date(),
    })
    .where(eq(emails.contactId, contactId));

  if (auditContext) {
    await logCompanyCreatedFromContact(auditContext.userId, contactId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return {
    company: newCompany,
    contact: updatedContact,
  };
};
