/**
 * Modul: CardDAV Contacts Sync
 * Zweck: Multi-Account CardDAV synchronization with addressbook selection
 * Abhängigkeiten: tsdav, server/db.js
 */

import { createLogger } from '../logger.js';
const log = createLogger('CardDAV');

import * as db from '../db.js';

// --------------------------------------------------------
// Helper Functions
// --------------------------------------------------------

/**
 * Parse vCard text into structured object
 * @param {string} vCardText - Raw vCard data
 * @returns {Object} Parsed vCard object
 */
function parseVCard(vCardText) {
  const lines = vCardText.split(/\r?\n/).filter(line => line.trim());
  const vcard = {
    uid: null,
    name: null,
    phones: [],
    emails: [],
    addresses: [],
    organization: null,
    jobTitle: null,
    website: null,
    birthday: null,
    photo: null,
    nickname: null,
    notes: null,
    categories: null,
  };

  for (let i = 0; i < lines.length; i++) {
    let line = lines[i];

    // Handle line folding (continuation lines start with space or tab)
    while (i + 1 < lines.length && /^[ \t]/.test(lines[i + 1])) {
      line += lines[i + 1].substring(1);
      i++;
    }

    const colonIndex = line.indexOf(':');
    if (colonIndex === -1) continue;

    const fullKey = line.substring(0, colonIndex);
    const value = line.substring(colonIndex + 1).trim();

    // Parse property and parameters
    const [prop, ...params] = fullKey.split(';');
    const property = prop.toUpperCase();

    switch (property) {
      case 'UID':
        vcard.uid = value;
        break;

      case 'FN':
        if (!vcard.name) vcard.name = value;
        break;

      case 'N':
        // N is fallback if FN is not present
        // Format: Family;Given;Middle;Prefix;Suffix
        if (!vcard.name) {
          const parts = value.split(';').filter(p => p);
          vcard.name = parts.join(' ').trim();
        }
        break;

      case 'TEL':
        const phoneType = extractType(params) || 'other';
        vcard.phones.push({ label: phoneType, value: value });
        break;

      case 'EMAIL':
        const emailType = extractType(params) || 'other';
        vcard.emails.push({ label: emailType, value: value });
        break;

      case 'ADR':
        // Format: POBox;Extended;Street;City;State;Postal;Country
        const adrParts = value.split(';');
        const adrType = extractType(params) || 'other';
        vcard.addresses.push({
          label: adrType,
          street: adrParts[2] || null,
          city: adrParts[3] || null,
          state: adrParts[4] || null,
          postalCode: adrParts[5] || null,
          country: adrParts[6] || null,
        });
        break;

      case 'ORG':
        vcard.organization = value;
        break;

      case 'TITLE':
        vcard.jobTitle = value;
        break;

      case 'URL':
        // Take first URL if multiple exist
        if (!vcard.website) vcard.website = value;
        break;

      case 'BDAY':
        // Parse birthday to ISO format (YYYY-MM-DD)
        vcard.birthday = parseBirthday(value);
        break;

      case 'PHOTO':
        // Handle base64 encoded photos
        if (params.some(p => p.toUpperCase().includes('ENCODING=BASE64') || p.toUpperCase().includes('ENCODING=B'))) {
          // Photo might span multiple lines in old vCard format
          vcard.photo = value;
        }
        break;

      case 'NICKNAME':
        vcard.nickname = value;
        break;

      case 'NOTE':
        vcard.notes = value;
        break;

      case 'CATEGORIES':
        vcard.categories = value;
        break;
    }
  }

  return vcard;
}

/**
 * Extract TYPE parameter from vCard property parameters
 * @param {Array<string>} params - Property parameters
 * @returns {string|null} Type value
 */
function extractType(params) {
  // Priority order: more specific types first
  const typeHierarchy = ['CELL', 'MOBILE', 'HOME', 'WORK', 'FAX', 'OTHER', 'VOICE'];

  let foundType = null;

  for (const param of params) {
    const upper = param.toUpperCase();
    if (upper.startsWith('TYPE=')) {
      return param.substring(5).toLowerCase();
    }
    // Some vCards use TYPE without =
    if (typeHierarchy.includes(upper)) {
      // Keep the most specific type (earlier in hierarchy)
      const currentIndex = typeHierarchy.indexOf(upper);
      const foundIndex = foundType ? typeHierarchy.indexOf(foundType.toUpperCase()) : -1;

      if (foundIndex === -1 || currentIndex < foundIndex) {
        foundType = upper.toLowerCase();
      }
    }
  }

  return foundType;
}

/**
 * Parse birthday from various vCard formats to ISO date
 * @param {string} value - Birthday value from vCard
 * @returns {string|null} ISO date (YYYY-MM-DD) or null
 */
function parseBirthday(value) {
  if (!value) return null;

  // Remove any non-numeric characters except hyphens
  const cleaned = value.replace(/[^\d-]/g, '');

  // Try ISO format (YYYY-MM-DD)
  if (/^\d{4}-\d{2}-\d{2}$/.test(cleaned)) {
    return cleaned;
  }

  // Try compact format (YYYYMMDD)
  if (/^\d{8}$/.test(cleaned)) {
    return `${cleaned.slice(0, 4)}-${cleaned.slice(4, 6)}-${cleaned.slice(6, 8)}`;
  }

  // Try year only
  if (/^\d{4}$/.test(cleaned)) {
    return `${cleaned}-01-01`;
  }

  return null;
}

// --------------------------------------------------------
// Account Management
// --------------------------------------------------------

/**
 * Test CardDAV connection
 * @param {string} cardavUrl - CardDAV server URL
 * @param {string} username - Username
 * @param {string} password - Password
 * @returns {Promise<Object>} { ok: true, addressbooks: [...] }
 */
async function testConnection(cardavUrl, username, password) {
  // Use mock if set (for testing)
  if (_testConnectionMock) {
    return _testConnectionMock(cardavUrl, username, password);
  }

  try {
    const { createDAVClient } = await import('tsdav');
    const client = await createDAVClient({
      serverUrl: cardavUrl,
      credentials: { username, password },
      authMethod: 'Basic',
      defaultAccountType: 'carddav',
    });

    const addressbooks = await client.fetchAddressBooks();
    if (!addressbooks.length) {
      throw new Error('Connected, but no addressbooks found.');
    }

    return { ok: true, addressbooks };
  } catch (err) {
    log.error('Connection test failed:', err.message);
    throw new Error(`CardDAV connection failed: ${err.message}`);
  }
}

/**
 * Add new CardDAV account
 * @param {string} name - Account display name
 * @param {string} cardavUrl - CardDAV server URL
 * @param {string} username - Username
 * @param {string} password - Password
 * @returns {Promise<Object>} { accountId, addressbooks }
 */
async function addAccount(name, cardavUrl, username, password) {
  try {
    // Validate inputs
    if (!name || !cardavUrl || !username || !password) {
      throw new Error('All fields required: name, cardavUrl, username, password');
    }

    // Test connection first
    const { addressbooks } = await testConnection(cardavUrl, username, password);

    // Check for duplicate
    const existing = db.get().prepare(
      'SELECT id FROM carddav_accounts WHERE carddav_url = ? AND username = ?'
    ).get(cardavUrl, username);

    if (existing) {
      throw new Error('Account with this URL and username already exists.');
    }

    // Warn if DB_ENCRYPTION_KEY not set
    if (!process.env.DB_ENCRYPTION_KEY) {
      log.warn('WARNING: DB_ENCRYPTION_KEY is not set - CardDAV credentials will be stored unencrypted.');
    }

    // Insert account
    const result = db.get().prepare(`
      INSERT INTO carddav_accounts (name, carddav_url, username, password)
      VALUES (?, ?, ?, ?)
    `).run(name, cardavUrl, username, password);

    const accountId = result.lastInsertRowid;

    // Insert addressbook selections (all enabled by default)
    const addressbookData = [];
    for (const abook of addressbooks) {
      const abookName = abook.displayName || 'Unnamed Addressbook';

      db.get().prepare(`
        INSERT INTO carddav_addressbook_selection (account_id, addressbook_url, addressbook_name, enabled)
        VALUES (?, ?, ?, 1)
      `).run(accountId, abook.url, abookName);

      addressbookData.push({ url: abook.url, name: abookName, enabled: true });
    }

    log.info(`Added CardDAV account "${name}" with ${addressbooks.length} addressbooks.`);

    const account = {
      id: accountId,
      name,
      cardavUrl,
      username,
      createdAt: new Date().toISOString(),
      lastSync: null
    };

    return { account, addressbooks: addressbookData };
  } catch (err) {
    log.error('Failed to add account:', err.message);
    throw err;
  }
}

/**
 * Get all CardDAV accounts
 * @returns {Array<Object>} Array of account objects (without passwords)
 */
function getAllAccounts() {
  try {
    const accounts = db.get().prepare(`
      SELECT id, name, carddav_url, username, created_at, last_sync
      FROM carddav_accounts
      ORDER BY created_at DESC
    `).all();

    return accounts.map(acc => ({
      id: acc.id,
      name: acc.name,
      cardavUrl: acc.carddav_url,
      username: acc.username,
      createdAt: acc.created_at,
      lastSync: acc.last_sync,
    }));
  } catch (err) {
    log.error('Failed to get accounts:', err.message);
    throw err;
  }
}

/**
 * Delete CardDAV account
 * @param {number} accountId - Account ID
 * @returns {Object} { success: true }
 */
function deleteAccount(accountId) {
  try {
    const account = db.get().prepare('SELECT * FROM carddav_accounts WHERE id = ?').get(accountId);
    if (!account) {
      throw new Error(`Account ${accountId} not found.`);
    }

    // CASCADE will delete carddav_addressbook_selection entries
    // Contacts will have carddav_account_id SET NULL (see migration)
    db.get().prepare('DELETE FROM carddav_accounts WHERE id = ?').run(accountId);

    log.info(`Deleted CardDAV account ${accountId} ("${account.name}").`);

    return { success: true };
  } catch (err) {
    log.error('Failed to delete account:', err.message);
    throw err;
  }
}

// --------------------------------------------------------
// Addressbook Discovery & Selection
// --------------------------------------------------------

/**
 * Discover addressbooks for an account (refresh from server)
 * @param {number} accountId - Account ID
 * @returns {Promise<Array<Object>>} Array of addressbook objects
 */
async function discoverAddressbooks(accountId) {
  try {
    const account = db.get().prepare('SELECT * FROM carddav_accounts WHERE id = ?').get(accountId);
    if (!account) {
      throw new Error(`Account ${accountId} not found.`);
    }

    const { addressbooks } = await testConnection(account.carddav_url, account.username, account.password);

    // UPSERT into carddav_addressbook_selection
    const result = [];
    for (const abook of addressbooks) {
      const abookName = abook.displayName || 'Unnamed Addressbook';

      // Check if exists
      const existing = db.get().prepare(`
        SELECT id, enabled FROM carddav_addressbook_selection
        WHERE account_id = ? AND addressbook_url = ?
      `).get(accountId, abook.url);

      if (existing) {
        // Update name only (preserve enabled state)
        db.get().prepare(`
          UPDATE carddav_addressbook_selection
          SET addressbook_name = ?
          WHERE id = ?
        `).run(abookName, existing.id);

        result.push({
          id: existing.id,
          url: abook.url,
          name: abookName,
          enabled: existing.enabled === 1
        });
      } else {
        // Insert new (enabled by default)
        const insertResult = db.get().prepare(`
          INSERT INTO carddav_addressbook_selection (account_id, addressbook_url, addressbook_name, enabled)
          VALUES (?, ?, ?, 1)
        `).run(accountId, abook.url, abookName);

        result.push({
          id: insertResult.lastInsertRowid,
          url: abook.url,
          name: abookName,
          enabled: true
        });
      }
    }

    log.info(`Discovered ${addressbooks.length} addressbooks for account ${accountId}.`);

    return result;
  } catch (err) {
    log.error('Failed to discover addressbooks:', err.message);
    throw err;
  }
}

/**
 * Toggle addressbook enabled state
 * @param {number} addressbookId - Addressbook selection ID
 * @param {boolean} enabled - Enable or disable
 * @returns {Object} { success: true }
 */
function toggleAddressbook(addressbookId, enabled) {
  try {
    const enabledValue = enabled ? 1 : 0;

    const result = db.get().prepare(`
      UPDATE carddav_addressbook_selection
      SET enabled = ?
      WHERE id = ?
    `).run(enabledValue, addressbookId);

    if (result.changes === 0) {
      throw new Error(`Addressbook ${addressbookId} not found.`);
    }

    log.info(`Addressbook ${addressbookId} ${enabled ? 'enabled' : 'disabled'}.`);

    return { success: true };
  } catch (err) {
    log.error('Failed to toggle addressbook:', err.message);
    throw err;
  }
}

// --------------------------------------------------------
// Contact Sync
// --------------------------------------------------------

/**
 * Sync all enabled addressbooks for an account
 * @param {number} accountId - Account ID
 * @returns {Promise<Object>} { synced, errors }
 */
async function syncAccount(accountId) {
  try {
    const account = db.get().prepare('SELECT * FROM carddav_accounts WHERE id = ?').get(accountId);
    if (!account) {
      throw new Error(`Account ${accountId} not found.`);
    }

    log.info(`Syncing CardDAV account ${accountId} ("${account.name}")...`);

    // Create tsdav client
    const { createDAVClient } = await import('tsdav');
    const client = await createDAVClient({
      serverUrl: account.carddav_url,
      credentials: { username: account.username, password: account.password },
      authMethod: 'Basic',
      defaultAccountType: 'carddav',
    });

    // Get enabled addressbooks for this account
    const enabledAddressbooks = db.get().prepare(`
      SELECT id, addressbook_url, addressbook_name
      FROM carddav_addressbook_selection
      WHERE account_id = ? AND enabled = 1
    `).all(accountId);

    if (enabledAddressbooks.length === 0) {
      log.info(`Account ${accountId}: no enabled addressbooks, skipping.`);
      return { synced: 0, errors: 0 };
    }

    let totalSynced = 0;
    let totalErrors = 0;

    // Fetch all addressbooks from server
    const serverAddressbooks = await client.fetchAddressBooks();

    for (const selAbook of enabledAddressbooks) {
      // Find matching addressbook from server
      const serverAbook = serverAddressbooks.find(sa => sa.url === selAbook.addressbook_url);

      if (!serverAbook) {
        log.warn(`Addressbook ${selAbook.addressbook_url} not found on server, disabling.`);
        db.get().prepare(`
          UPDATE carddav_addressbook_selection SET enabled = 0
          WHERE id = ?
        `).run(selAbook.id);
        continue;
      }

      // Sync this addressbook
      const { synced, errors } = await syncAddressbook(accountId, selAbook.addressbook_url, client, serverAbook);
      totalSynced += synced;
      totalErrors += errors;
    }

    // Update last_sync for account
    db.get().prepare(`
      UPDATE carddav_accounts SET last_sync = ? WHERE id = ?
    `).run(new Date().toISOString(), accountId);

    log.info(`Account ${accountId} sync complete: ${totalSynced} contacts synced, ${totalErrors} errors.`);

    return { synced: totalSynced, errors: totalErrors };
  } catch (err) {
    log.error(`Sync failed for account ${accountId}:`, err.message);
    throw err;
  }
}

/**
 * Sync a specific addressbook
 * @param {number} accountId - Account ID
 * @param {string} addressbookUrl - Addressbook URL
 * @param {Object} client - tsdav client instance (optional, will create if not provided)
 * @param {Object} serverAddressbook - Server addressbook object (optional)
 * @returns {Promise<Object>} { synced, errors }
 */
async function syncAddressbook(accountId, addressbookUrl, client = null, serverAddressbook = null) {
  try {
    const account = db.get().prepare('SELECT * FROM carddav_accounts WHERE id = ?').get(accountId);
    if (!account) {
      throw new Error(`Account ${accountId} not found.`);
    }

    // Create client if not provided
    if (!client) {
      const { createDAVClient } = await import('tsdav');
      client = await createDAVClient({
        serverUrl: account.carddav_url,
        credentials: { username: account.username, password: account.password },
        authMethod: 'Basic',
        defaultAccountType: 'carddav',
      });
    }

    // Find addressbook if not provided
    if (!serverAddressbook) {
      const addressbooks = await client.fetchAddressBooks();
      serverAddressbook = addressbooks.find(ab => ab.url === addressbookUrl);

      if (!serverAddressbook) {
        throw new Error(`Addressbook ${addressbookUrl} not found on server.`);
      }
    }

    // Fetch vCards from addressbook
    let vcardObjects;
    try {
      vcardObjects = await client.fetchVCards({ addressBook: serverAddressbook });
    } catch (err) {
      log.error(`Failed to fetch vCards from ${addressbookUrl}:`, err.message);
      return { synced: 0, errors: 1 };
    }

    let synced = 0;
    let errors = 0;

    // Parse and merge each vCard
    for (const vcardObj of vcardObjects) {
      try {
        const vCardText = vcardObj.data || '';
        if (!vCardText.trim()) continue;

        await parseAndMergeContact(vCardText, accountId, addressbookUrl);
        synced++;
      } catch (err) {
        log.error(`Failed to parse/merge vCard:`, err.message);
        errors++;
      }
    }

    log.info(`Addressbook ${addressbookUrl}: ${synced} contacts synced, ${errors} errors.`);

    return { synced, errors };
  } catch (err) {
    log.error(`Failed to sync addressbook ${addressbookUrl}:`, err.message);
    throw err;
  }
}

/**
 * Parse vCard and merge with existing contact using Smart Merge Logic
 * @param {string} vCardText - Raw vCard data
 * @param {number} accountId - Account ID
 * @param {string} addressbookUrl - Addressbook URL
 * @returns {Promise<number>} Contact ID
 */
async function parseAndMergeContact(vCardText, accountId, addressbookUrl) {
  try {
    const vcard = parseVCard(vCardText);

    if (!vcard.uid) {
      throw new Error('vCard missing UID, skipping.');
    }

    if (!vcard.name) {
      throw new Error('vCard missing name (FN/N), skipping.');
    }

    // Smart Merge Logic (see design doc)

    // Step 1: Check for existing contact by cardav_uid
    let contact = db.get().prepare(`
      SELECT * FROM contacts
      WHERE carddav_account_id = ? AND carddav_addressbook_url = ? AND carddav_uid = ?
    `).get(accountId, addressbookUrl, vcard.uid);

    if (contact) {
      // Update existing contact (only fill NULL fields to preserve manual changes)
      updateContact(contact.id, vcard, false);
      updateContactMultiValues(contact.id, vcard);
      return contact.id;
    }

    // Step 2: Check for existing contact by email or phone match
    contact = findContactByEmailOrPhone(vcard.emails, vcard.phones);

    if (contact) {
      // Update existing contact and establish CardDAV link
      updateContact(contact.id, vcard, true);

      // Set CardDAV link
      db.get().prepare(`
        UPDATE contacts
        SET carddav_account_id = ?, carddav_uid = ?, carddav_addressbook_url = ?
        WHERE id = ?
      `).run(accountId, vcard.uid, addressbookUrl, contact.id);

      updateContactMultiValues(contact.id, vcard);
      return contact.id;
    }

    // Step 3: No match - insert new contact
    const result = db.get().prepare(`
      INSERT INTO contacts (
        name, category, organization, job_title, birthday, website,
        photo, nickname, notes,
        carddav_account_id, carddav_uid, carddav_addressbook_url
      )
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      vcard.name,
      vcard.categories || 'Sonstiges',
      vcard.organization,
      vcard.jobTitle,
      vcard.birthday,
      vcard.website,
      vcard.photo,
      vcard.nickname,
      vcard.notes,
      accountId,
      vcard.uid,
      addressbookUrl
    );

    const contactId = result.lastInsertRowid;

    // Insert multi-value fields
    insertContactMultiValues(contactId, vcard);

    return contactId;
  } catch (err) {
    log.error('Failed to parse and merge contact:', err.message);
    throw err;
  }
}

/**
 * Find existing contact by email or phone match
 * @param {Array<Object>} emails - Array of email objects
 * @param {Array<Object>} phones - Array of phone objects
 * @returns {Object|null} Contact object or null
 */
function findContactByEmailOrPhone(emails, phones) {
  // Try email match first
  for (const email of emails) {
    const contact = db.get().prepare(`
      SELECT c.* FROM contacts c
      LEFT JOIN contact_emails ce ON c.id = ce.contact_id
      WHERE c.email = ? OR ce.value = ?
      LIMIT 1
    `).get(email.value, email.value);

    if (contact) return contact;
  }

  // Try phone match
  for (const phone of phones) {
    const contact = db.get().prepare(`
      SELECT c.* FROM contacts c
      LEFT JOIN contact_phones cp ON c.id = cp.contact_id
      WHERE c.phone = ? OR cp.value = ?
      LIMIT 1
    `).get(phone.value, phone.value);

    if (contact) return contact;
  }

  return null;
}

/**
 * Update existing contact with vCard data (only NULL fields)
 * @param {number} contactId - Contact ID
 * @param {Object} vcard - Parsed vCard object
 * @param {boolean} fillAll - If true, update all fields; if false, only update NULL fields
 */
function updateContact(contactId, vcard, fillAll = false) {
  const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(contactId);
  if (!contact) return;

  const updates = [];
  const values = [];

  // Helper to conditionally update field
  const maybeUpdate = (field, dbColumn, vcardValue) => {
    if (vcardValue !== null && vcardValue !== undefined) {
      if (fillAll || contact[dbColumn] === null) {
        updates.push(`${dbColumn} = ?`);
        values.push(vcardValue);
      }
    }
  };

  maybeUpdate('name', 'name', vcard.name);
  maybeUpdate('organization', 'organization', vcard.organization);
  maybeUpdate('jobTitle', 'job_title', vcard.jobTitle);
  maybeUpdate('birthday', 'birthday', vcard.birthday);
  maybeUpdate('website', 'website', vcard.website);
  maybeUpdate('photo', 'photo', vcard.photo);
  maybeUpdate('nickname', 'nickname', vcard.nickname);
  maybeUpdate('notes', 'notes', vcard.notes);
  maybeUpdate('categories', 'category', vcard.categories);

  if (updates.length === 0) return;

  values.push(contactId);

  db.get().prepare(`
    UPDATE contacts SET ${updates.join(', ')} WHERE id = ?
  `).run(...values);
}

/**
 * Update contact multi-value fields (phones, emails, addresses)
 * Preserves primary entries, replaces non-primary entries
 * @param {number} contactId - Contact ID
 * @param {Object} vcard - Parsed vCard object
 */
function updateContactMultiValues(contactId, vcard) {
  const transaction = db.get().transaction(() => {
    // Delete non-primary entries
    db.get().prepare('DELETE FROM contact_phones WHERE contact_id = ? AND is_primary = 0').run(contactId);
    db.get().prepare('DELETE FROM contact_emails WHERE contact_id = ? AND is_primary = 0').run(contactId);
    db.get().prepare('DELETE FROM contact_addresses WHERE contact_id = ? AND is_primary = 0').run(contactId);

    // Insert new entries from vCard
    insertContactMultiValues(contactId, vcard);
  });

  transaction();
}

/**
 * Insert contact multi-value fields (phones, emails, addresses)
 * @param {number} contactId - Contact ID
 * @param {Object} vcard - Parsed vCard object
 */
function insertContactMultiValues(contactId, vcard) {
  // Check if primary entries exist
  const hasPrimaryPhone = db.get().prepare(
    'SELECT 1 FROM contact_phones WHERE contact_id = ? AND is_primary = 1'
  ).get(contactId);

  const hasPrimaryEmail = db.get().prepare(
    'SELECT 1 FROM contact_emails WHERE contact_id = ? AND is_primary = 1'
  ).get(contactId);

  const hasPrimaryAddress = db.get().prepare(
    'SELECT 1 FROM contact_addresses WHERE contact_id = ? AND is_primary = 1'
  ).get(contactId);

  // Batch insert phones
  if (vcard.phones && vcard.phones.length > 0) {
    const placeholders = vcard.phones.map(() => '(?, ?, ?, ?)').join(', ');
    const values = vcard.phones.flatMap((phone, i) => [
      contactId,
      phone.label || null,
      phone.value,
      (!hasPrimaryPhone && i === 0) ? 1 : 0
    ]);

    db.get().prepare(`
      INSERT INTO contact_phones (contact_id, label, value, is_primary)
      VALUES ${placeholders}
    `).run(...values);
  }

  // Batch insert emails
  if (vcard.emails && vcard.emails.length > 0) {
    const placeholders = vcard.emails.map(() => '(?, ?, ?, ?)').join(', ');
    const values = vcard.emails.flatMap((email, i) => [
      contactId,
      email.label || null,
      email.value,
      (!hasPrimaryEmail && i === 0) ? 1 : 0
    ]);

    db.get().prepare(`
      INSERT INTO contact_emails (contact_id, label, value, is_primary)
      VALUES ${placeholders}
    `).run(...values);
  }

  // Batch insert addresses
  if (vcard.addresses && vcard.addresses.length > 0) {
    const placeholders = vcard.addresses.map(() => '(?, ?, ?, ?, ?, ?, ?, ?)').join(', ');
    const values = vcard.addresses.flatMap((addr, i) => [
      contactId,
      addr.label || null,
      addr.street,
      addr.city,
      addr.state,
      addr.postalCode,
      addr.country,
      (!hasPrimaryAddress && i === 0) ? 1 : 0
    ]);

    db.get().prepare(`
      INSERT INTO contact_addresses (contact_id, label, street, city, state, postal_code, country, is_primary)
      VALUES ${placeholders}
    `).run(...values);
  }
}

// --------------------------------------------------------
// Exports
// --------------------------------------------------------

export {
  // Account Management
  addAccount,
  getAllAccounts,
  deleteAccount,
  testConnection,

  // Addressbook Discovery
  discoverAddressbooks,
  toggleAddressbook,

  // Contact Sync
  syncAccount,
  syncAddressbook,
  parseAndMergeContact,

  // Helpers (exported for testing)
  parseVCard,
  _mockTestConnection,
};

// --------------------------------------------------------
// Test Mocking Support
// --------------------------------------------------------

let _testConnectionMock = null;

/**
 * ONLY FOR TESTING: Mock testConnection for unit tests
 * @param {Function|null} mockFn - Mock function or null to reset
 */
function _mockTestConnection(mockFn) {
  _testConnectionMock = mockFn;
}
