/**
 * IT Glue Sync Service — Part 1 of 2
 * Syncs all IT Glue data to local itg_* PostgreSQL tables
 */

import postgresClient from './postgres-client';
import { getITGlueClient } from './itglue-client';

export interface ITGlueSyncEntityResult {
  entity: string;
  success: boolean;
  recordsUpserted: number;
  duration: number;
  error?: string;
}

export interface ITGlueSyncResult {
  syncId: number;
  syncType: 'full';
  status: 'completed' | 'failed';
  startedAt: Date;
  completedAt: Date;
  duration: number;
  entities: ITGlueSyncEntityResult[];
  totalUpserted: number;
  errors: string[];
}

export class ITGlueSyncService {
  private isSyncing = false;

  isSyncInProgress(): boolean { return this.isSyncing; }

  async fullSync(triggeredBy = 'system'): Promise<ITGlueSyncResult> {
    if (this.isSyncing) throw new Error('IT Glue sync already in progress');
    this.isSyncing = true;

    const startedAt = new Date();
    const entities: ITGlueSyncEntityResult[] = [];
    const errors: string[] = [];

    const { rows } = await postgresClient.query(
      `INSERT INTO itg_sync_history (sync_type, status, triggered_by, started_at)
       VALUES ('full','running',$1,NOW()) RETURNING id`,
      [triggeredBy]
    );
    const syncId = rows[0].id;

    const run = async (name: string, fn: () => Promise<number>) => {
      const t = Date.now();
      try {
        const count = await fn();
        entities.push({ entity: name, success: true, recordsUpserted: count, duration: Date.now() - t });
        console.log(`[ITGlue] ${name}: ${count} records`);
      } catch (err: any) {
        errors.push(`${name}: ${err.message}`);
        entities.push({ entity: name, success: false, recordsUpserted: 0, duration: Date.now() - t, error: err.message });
        console.error(`[ITGlue] ${name} FAILED:`, err.message);
      }
    };

    try {
      await run('organization_types',       () => this.syncSimpleTable('/organization_types',  'itg_organization_types'));
      await run('organization_statuses',    () => this.syncSimpleTable('/organization_statuses','itg_organization_statuses'));
      await run('configuration_types',      () => this.syncSimpleTable('/configuration_types', 'itg_configuration_types'));
      await run('configuration_statuses',   () => this.syncSimpleTable('/configuration_statuses','itg_configuration_statuses'));
      await run('contact_types',            () => this.syncSimpleTable('/contact_types',       'itg_contact_types'));
      await run('password_categories',      () => this.syncSimpleTable('/password_categories', 'itg_password_categories'));
      await run('manufacturers',            () => this.syncSimpleTable('/manufacturers',       'itg_manufacturers'));
      await run('operating_systems',        () => this.syncSimpleTable('/operating_systems',   'itg_operating_systems'));
      await run('platforms',                () => this.syncSimpleTable('/platforms',           'itg_platforms'));
      await run('countries',                () => this.syncCountries());
      await run('models',                   () => this.syncModels());
      await run('flexible_asset_types',     () => this.syncFlexibleAssetTypes());
      await run('flexible_asset_fields',    () => this.syncFlexibleAssetFields());
      await run('organizations',            () => this.syncOrganizations());
      await run('locations',                () => this.syncLocations());
      await run('contacts',                 () => this.syncContacts());
      await run('configurations',           () => this.syncConfigurations());
      // configuration_interfaces skipped — no flat API endpoint; per-config calls are too slow for 14k+ configs
      await run('flexible_assets',          () => this.syncFlexibleAssets());
      await run('password_folders',         () => this.syncPasswordFolders());
      await run('passwords',                () => this.syncPasswords());
      await run('documents',                () => this.syncDocuments());
      await run('domains',                  () => this.syncDomains());
      await run('expirations',              () => this.syncExpirations());
    } finally {
      this.isSyncing = false;
    }

    const completedAt = new Date();
    const duration = completedAt.getTime() - startedAt.getTime();
    const totalUpserted = entities.reduce((s, e) => s + e.recordsUpserted, 0);
    const status = errors.length === 0 ? 'completed' : 'failed';

    await postgresClient.query(
      `UPDATE itg_sync_history
       SET status=$1, completed_at=NOW(), duration_ms=$2, entities=$3, error=$4, total_upserted=$5
       WHERE id=$6`,
      [status, duration, JSON.stringify(entities), errors.join('\n') || null, totalUpserted, syncId]
    );

    return { syncId, syncType: 'full', status, startedAt, completedAt, duration, entities, totalUpserted, errors };
  }

  // ─── Generic simple-table upsert (id, name, created_at, updated_at) ──────────

  private async syncSimpleTable(path: string, table: string): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages(path);
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO ${table} (id, name, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,NOW())
         ON CONFLICT (id) DO UPDATE SET name=$2, updated_at=$4, synced_at=NOW()`,
        [item.id, a.name, a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncCountries(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/countries');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_countries (id, name, iso_code, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,NOW())
         ON CONFLICT (id) DO UPDATE SET name=$2, iso_code=$3, updated_at=$5, synced_at=NOW()`,
        [item.id, a.name, a['iso-code'] || null, a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncModels(): Promise<number> {
    const client = getITGlueClient();
    const mfrs = await client.getRawAllPages('/manufacturers');
    let count = 0;
    for (const mfr of mfrs) {
      const models = await client.getRawAllPages(`/manufacturers/${mfr.id}/relationships/models`);
      for (const item of models) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_models (id, manufacturer_id, name, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,NOW())
           ON CONFLICT (id) DO UPDATE SET manufacturer_id=$2, name=$3, updated_at=$5, synced_at=NOW()`,
          [item.id, mfr.id, a.name, a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncFlexibleAssetTypes(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/flexible_asset_types');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_flexible_asset_types (id, name, description, icon, enabled, builtin, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW())
         ON CONFLICT (id) DO UPDATE SET name=$2, description=$3, icon=$4, enabled=$5, builtin=$6, updated_at=$8, synced_at=NOW()`,
        [item.id, a.name, a.description || null, a.icon || null, a.enabled ?? true, a.builtin ?? false,
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncFlexibleAssetFields(): Promise<number> {
    const client = getITGlueClient();
    const types = await client.getRawAllPages('/flexible_asset_types');
    let count = 0;
    for (const type of types) {
      const fields = await client.getRawAllPages(
        `/flexible_asset_types/${type.id}/relationships/flexible_asset_fields`
      );
      for (const item of fields) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_flexible_asset_fields
             (id, flexible_asset_type_id, name, kind, hint, decimals, tag_type,
              required, use_for_title, expiration, show_in_list, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,NOW())
           ON CONFLICT (id) DO UPDATE SET
             flexible_asset_type_id=$2, name=$3, kind=$4, hint=$5, decimals=$6, tag_type=$7,
             required=$8, use_for_title=$9, expiration=$10, show_in_list=$11, updated_at=$13, synced_at=NOW()`,
          [item.id, type.id, a.name, a.kind || null, a.hint || null, a.decimals || 0,
           a['tag-type'] || null, a.required ?? false, a['use-for-title'] ?? false,
           a.expiration ?? false, a['show-in-list'] ?? false,
           a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncOrganizations(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/organizations');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_organizations
           (id, name, short_name, organization_type_id, organization_type_name,
            organization_status_id, organization_status_name, psa_integration, psa_id,
            sync_active, primary_org, quick_notes, description, alert, parent_id,
            created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,NOW())
         ON CONFLICT (id) DO UPDATE SET
           name=$2, short_name=$3, organization_type_id=$4, organization_type_name=$5,
           organization_status_id=$6, organization_status_name=$7, psa_integration=$8,
           psa_id=$9, sync_active=$10, primary_org=$11, quick_notes=$12, description=$13,
           alert=$14, parent_id=$15, updated_at=$17, synced_at=NOW()`,
        [item.id, a.name, a['short-name'] || null,
         a['organization-type-id'] || null, a['organization-type-name'] || null,
         a['organization-status-id'] || null, a['organization-status-name'] || null,
         a['psa-integration'] || null, a['psa-id'] || null,
         a['sync-active'] ?? false, a.primary ?? false,
         a['quick-notes'] || null, a.description || null, a.alert || null,
         a['parent-id'] || null, a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncLocations(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/locations');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_locations
           (id, organization_id, organization_name, name, primary_location,
            address_1, address_2, city, region_name, postal_code, country_name,
            phone, fax, notes, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,NOW())
         ON CONFLICT (id) DO UPDATE SET
           organization_id=$2, organization_name=$3, name=$4, primary_location=$5,
           address_1=$6, address_2=$7, city=$8, region_name=$9, postal_code=$10,
           country_name=$11, phone=$12, fax=$13, notes=$14, updated_at=$16, synced_at=NOW()`,
        [item.id, a['organization-id'], a['organization-name'] || null, a.name,
         a['primary-location'] ?? false, a['address-1'] || null, a['address-2'] || null,
         a.city || null, a['region-name'] || null, a['postal-code'] || null,
         a['country-name'] || null, a.phone || null, a.fax || null, a.notes || null,
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncContacts(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/contacts');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_contacts
           (id, organization_id, organization_name, first_name, last_name, name,
            title, contact_type_id, contact_type_name, location_id, important,
            notes, emails, phones, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,NOW())
         ON CONFLICT (id) DO UPDATE SET
           organization_id=$2, organization_name=$3, first_name=$4, last_name=$5,
           name=$6, title=$7, contact_type_id=$8, contact_type_name=$9,
           location_id=$10, important=$11, notes=$12, emails=$13, phones=$14,
           updated_at=$16, synced_at=NOW()`,
        [item.id, a['organization-id'], a['organization-name'] || null,
         a['first-name'] || null, a['last-name'] || null, a.name || null,
         a.title || null, a['contact-type-id'] || null, a['contact-type-name'] || null,
         a['location-id'] || null, a.important ?? false, a.notes || null,
         JSON.stringify(a['contact-emails'] || []),
         JSON.stringify(a['contact-phones'] || []),
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncConfigurations(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/configurations');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_configurations
           (id, organization_id, organization_name, name, hostname, primary_ip,
            mac_address, serial_number, asset_tag, position, installed_by, purchased_by,
            notes, operating_system_notes, warranty_expires_at, installed_at, purchased_at,
            end_of_life_at, configuration_type_id, configuration_type_name,
            configuration_status_id, configuration_status_name,
            manufacturer_id, manufacturer_name, model_id, model_name,
            operating_system_id, operating_system_name, location_id, contact_id,
            rmm_id, rmm_integration_type, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,NOW())
         ON CONFLICT (id) DO UPDATE SET
           organization_id=$2, organization_name=$3, name=$4, hostname=$5, primary_ip=$6,
           mac_address=$7, serial_number=$8, asset_tag=$9, position=$10, installed_by=$11,
           purchased_by=$12, notes=$13, operating_system_notes=$14, warranty_expires_at=$15,
           installed_at=$16, purchased_at=$17, end_of_life_at=$18,
           configuration_type_id=$19, configuration_type_name=$20,
           configuration_status_id=$21, configuration_status_name=$22,
           manufacturer_id=$23, manufacturer_name=$24, model_id=$25, model_name=$26,
           operating_system_id=$27, operating_system_name=$28,
           location_id=$29, contact_id=$30, rmm_id=$31, rmm_integration_type=$32,
           updated_at=$34, synced_at=NOW()`,
        [item.id, a['organization-id'], a['organization-name'] || null,
         a.name, a.hostname || null, a['primary-ip'] || null,
         a['mac-address'] || null, a['serial-number'] || null, a['asset-tag'] || null,
         a.position || null, a['installed-by'] || null, a['purchased-by'] || null,
         a.notes || null, a['operating-system-notes'] || null,
         a['warranty-expires-at'] || null, a['installed-at'] || null,
         a['purchased-at'] || null, a['end-of-life-at'] || null,
         a['configuration-type-id'] || null, a['configuration-type-name'] || null,
         a['configuration-status-id'] || null, a['configuration-status-name'] || null,
         a['manufacturer-id'] || null, a['manufacturer-name'] || null,
         a['model-id'] || null, a['model-name'] || null,
         a['operating-system-id'] || null, a['operating-system-name'] || null,
         a['location-id'] || null, a['contact-id'] || null,
         a['rmm-id'] || null, a['rmm-integration-type'] || null,
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncConfigurationInterfaces(): Promise<number> {
    const client = getITGlueClient();
    // No flat endpoint — must iterate per configuration
    const configs = await client.getRawAllPages('/configurations');
    let count = 0;
    for (const cfg of configs) {
      const items = await client.getRawAllPages(
        `/configurations/${cfg.id}/relationships/configuration_interfaces`
      );
      for (const item of items) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_configuration_interfaces
             (id, configuration_id, organization_id, name, ip_address, mac_address,
              primary_interface, notes, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW())
           ON CONFLICT (id) DO UPDATE SET
             configuration_id=$2, organization_id=$3, name=$4, ip_address=$5,
             mac_address=$6, primary_interface=$7, notes=$8, updated_at=$10, synced_at=NOW()`,
          [item.id, cfg.id, a['organization-id'] || null,
           a.name || null, a['ip-address'] || null, a['mac-address'] || null,
           a.primary ?? false, a.notes || null,
           a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncFlexibleAssets(): Promise<number> {
    const client = getITGlueClient();
    // API requires filter[flexible-asset-type-id] — iterate per type
    const types = await client.getRawAllPages('/flexible_asset_types');
    let count = 0;
    for (const type of types) {
      const items = await client.getRawAllPages('/flexible_assets', {
        'filter[flexible-asset-type-id]': type.id,
      });
      for (const item of items) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_flexible_assets
             (id, organization_id, organization_name, flexible_asset_type_id,
              flexible_asset_type_name, name, traits, archived, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW())
           ON CONFLICT (id) DO UPDATE SET
             organization_id=$2, organization_name=$3, flexible_asset_type_id=$4,
             flexible_asset_type_name=$5, name=$6, traits=$7, archived=$8,
             updated_at=$10, synced_at=NOW()`,
          [item.id, a['organization-id'], a['organization-name'] || null,
           a['flexible-asset-type-id'], a['flexible-asset-type-name'] || null,
           a.name || null, JSON.stringify(a.traits || {}), a.archived ?? false,
           a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncPasswordFolders(): Promise<number> {
    const client = getITGlueClient();
    // No flat endpoint — must iterate per organization
    const orgs = await postgresClient.query('SELECT id FROM itg_organizations');
    let count = 0;
    for (const org of orgs.rows) {
      const items = await client.getRawAllPages(
        `/organizations/${org.id}/relationships/password_folders`
      );
      for (const item of items) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_password_folders
             (id, organization_id, organization_name, name, inherited, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,NOW())
           ON CONFLICT (id) DO UPDATE SET
             organization_id=$2, organization_name=$3, name=$4, inherited=$5,
             updated_at=$7, synced_at=NOW()`,
          [item.id, org.id, a['organization-name'] || null,
           a.name, a.inherited ?? false, a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncPasswords(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/passwords');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_passwords
           (id, organization_id, organization_name, name, username, password, url,
            notes, password_category_id, password_category_name, password_folder_id,
            autofill_selectors, otp_enabled, archived, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,NOW())
         ON CONFLICT (id) DO UPDATE SET
           organization_id=$2, organization_name=$3, name=$4, username=$5, password=$6,
           url=$7, notes=$8, password_category_id=$9, password_category_name=$10,
           password_folder_id=$11, autofill_selectors=$12, otp_enabled=$13, archived=$14,
           updated_at=$16, synced_at=NOW()`,
        [item.id, a['organization-id'], a['organization-name'] || null,
         a.name, a.username || null, a.password || null, a.url || null,
         a.notes || null, a['password-category-id'] || null, a['password-category-name'] || null,
         a['password-folder-id'] || null, a['autofill-selectors'] || null,
         a['otp-enabled'] ?? false, a.archived ?? false,
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncDocuments(): Promise<number> {
    const client = getITGlueClient();
    // No flat endpoint — must iterate per organization
    const orgs = await postgresClient.query('SELECT id FROM itg_organizations');
    let count = 0;
    for (const org of orgs.rows) {
      const items = await client.getRawAllPages(
        `/organizations/${org.id}/relationships/documents`
      );
      for (const item of items) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_documents
             (id, organization_id, organization_name, name, content, draft, archived, created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,NOW())
           ON CONFLICT (id) DO UPDATE SET
             organization_id=$2, organization_name=$3, name=$4, content=$5,
             draft=$6, archived=$7, updated_at=$9, synced_at=NOW()`,
          [item.id, org.id, a['organization-name'] || null,
           a.name, a.content || null, a.draft ?? false, a.archived ?? false,
           a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  private async syncDomains(): Promise<number> {
    const client = getITGlueClient();
    const items = await client.getRawAllPages('/domains');
    let count = 0;
    for (const item of items) {
      const a = item.attributes;
      await postgresClient.query(
        `INSERT INTO itg_domains
           (id, organization_id, organization_name, name, screenshot, whois_updated_at,
            expires_at, registrar_name, notes, created_at, updated_at, synced_at)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,NOW())
         ON CONFLICT (id) DO UPDATE SET
           organization_id=$2, organization_name=$3, name=$4, screenshot=$5,
           whois_updated_at=$6, expires_at=$7, registrar_name=$8, notes=$9,
           updated_at=$11, synced_at=NOW()`,
        [item.id, a['organization-id'], a['organization-name'] || null,
         a.name, a.screenshot || null, a['whois-updated-at'] || null,
         a['expires-at'] || null, a['registrar-name'] || null, a.notes || null,
         a['created-at'] || null, a['updated-at'] || null]
      );
      count++;
    }
    return count;
  }

  private async syncExpirations(): Promise<number> {
    const client = getITGlueClient();
    // No flat endpoint — must iterate per organization
    const orgs = await postgresClient.query('SELECT id FROM itg_organizations');
    let count = 0;
    for (const org of orgs.rows) {
      const items = await client.getRawAllPages(
        `/organizations/${org.id}/relationships/expirations`
      );
      for (const item of items) {
        const a = item.attributes;
        await postgresClient.query(
          `INSERT INTO itg_expirations
             (id, organization_id, organization_name, resource_id, resource_type,
              resource_name, expiration_type, description, expiration_date, notify,
              created_at, updated_at, synced_at)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,NOW())
           ON CONFLICT (id) DO UPDATE SET
             organization_id=$2, organization_name=$3, resource_id=$4, resource_type=$5,
             resource_name=$6, expiration_type=$7, description=$8, expiration_date=$9,
             notify=$10, updated_at=$12, synced_at=NOW()`,
          [item.id, org.id, a['organization-name'] || null,
           a['resource-id'] || null, a['resource-type'] || null, a['resource-name'] || null,
           a['expiration-type'] || null, a.description || null,
           a['expiration-date'] || null, a.notify ?? false,
           a['created-at'] || null, a['updated-at'] || null]
        );
        count++;
      }
    }
    return count;
  }

  /**
   * Re-fetch a single configuration from IT Glue and upsert the mirror row.
   * Mirrors the bulk syncConfigurations upsert. Used after a write so the
   * UI sees the new value immediately.
   */
  async refreshConfigurationById(id: string | number): Promise<void> {
    const client = getITGlueClient();
    const item = await client.getRawSingle(`/configurations/${id}`);
    if (!item) {
      throw new Error(`IT Glue refreshConfigurationById ${id}: empty response`);
    }
    const a = item.attributes;
    await postgresClient.query(
      `INSERT INTO itg_configurations
         (id, organization_id, organization_name, name, hostname, primary_ip,
          mac_address, serial_number, asset_tag, position, installed_by, purchased_by,
          notes, operating_system_notes, warranty_expires_at, installed_at, purchased_at,
          end_of_life_at, configuration_type_id, configuration_type_name,
          configuration_status_id, configuration_status_name,
          manufacturer_id, manufacturer_name, model_id, model_name,
          operating_system_id, operating_system_name, location_id, contact_id,
          rmm_id, rmm_integration_type, created_at, updated_at, synced_at)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,NOW())
       ON CONFLICT (id) DO UPDATE SET
         organization_id=$2, organization_name=$3, name=$4, hostname=$5, primary_ip=$6,
         mac_address=$7, serial_number=$8, asset_tag=$9, position=$10, installed_by=$11,
         purchased_by=$12, notes=$13, operating_system_notes=$14, warranty_expires_at=$15,
         installed_at=$16, purchased_at=$17, end_of_life_at=$18,
         configuration_type_id=$19, configuration_type_name=$20,
         configuration_status_id=$21, configuration_status_name=$22,
         manufacturer_id=$23, manufacturer_name=$24, model_id=$25, model_name=$26,
         operating_system_id=$27, operating_system_name=$28,
         location_id=$29, contact_id=$30, rmm_id=$31, rmm_integration_type=$32,
         updated_at=$34, synced_at=NOW()`,
      [
        item.id,
        a['organization-id'],
        a['organization-name'] || null,
        a.name,
        a.hostname || null,
        a['primary-ip'] || null,
        a['mac-address'] || null,
        a['serial-number'] || null,
        a['asset-tag'] || null,
        a.position || null,
        a['installed-by'] || null,
        a['purchased-by'] || null,
        a.notes || null,
        a['operating-system-notes'] || null,
        a['warranty-expires-at'] || null,
        a['installed-at'] || null,
        a['purchased-at'] || null,
        a['end-of-life-at'] || null,
        a['configuration-type-id'] || null,
        a['configuration-type-name'] || null,
        a['configuration-status-id'] || null,
        a['configuration-status-name'] || null,
        a['manufacturer-id'] || null,
        a['manufacturer-name'] || null,
        a['model-id'] || null,
        a['model-name'] || null,
        a['operating-system-id'] || null,
        a['operating-system-name'] || null,
        a['location-id'] || null,
        a['contact-id'] || null,
        a['rmm-id'] || null,
        a['rmm-integration-type'] || null,
        a['created-at'] || null,
        a['updated-at'] || null,
      ]
    );
  }

  /**
   * Re-fetch a single flexible asset from IT Glue and upsert the mirror row.
   * Used after a write to keep itg_flexible_assets in sync without running
   * the full 27-entity sync.
   */
  async refreshFlexibleAssetById(id: string | number): Promise<void> {
    const client = getITGlueClient();
    const item = await client.getRawSingle(`/flexible_assets/${id}`);
    if (!item) {
      throw new Error(`IT Glue refreshFlexibleAssetById ${id}: empty response`);
    }
    const a = item.attributes;
    await postgresClient.query(
      `INSERT INTO itg_flexible_assets
         (id, organization_id, organization_name, flexible_asset_type_id,
          flexible_asset_type_name, name, traits, archived, created_at, updated_at, synced_at)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,NOW())
       ON CONFLICT (id) DO UPDATE SET
         organization_id=$2, organization_name=$3, flexible_asset_type_id=$4,
         flexible_asset_type_name=$5, name=$6, traits=$7, archived=$8,
         updated_at=$10, synced_at=NOW()`,
      [
        item.id,
        a['organization-id'],
        a['organization-name'] || null,
        a['flexible-asset-type-id'],
        a['flexible-asset-type-name'] || null,
        a.name || null,
        JSON.stringify(a.traits || {}),
        a.archived ?? false,
        a['created-at'] || null,
        a['updated-at'] || null,
      ]
    );
  }
}

let _instance: ITGlueSyncService | null = null;
export function getITGlueSyncService(): ITGlueSyncService {
  if (!_instance) _instance = new ITGlueSyncService();
  return _instance;
}
