/**
 * Generic REST CRM Adapter — default for new integrators.
 *
 * Talks to any REST API that exposes contact endpoints following a
 * configurable convention. Configuration lives in `appSettings` under the key
 * `crm_adapter_config` (encrypted JSON) and is editable via the Settings UI:
 *
 * {
 *   "baseUrl": "https://crm.example.com/api",
 *   "authHeader": "Authorization",
 *   "authValue": "Bearer xxx",                 // or "Basic ..." etc.
 *   "endpoints": {
 *     "listContacts":   "/contacts",           // GET, returns { data: [...] }
 *     "getContact":     "/contacts/{id}",      // GET
 *     "pollConversions":"/conversions?since={since_iso}", // GET, optional
 *     "listSegments":   "/segments"            // GET, optional
 *   },
 *   "fieldMap": {                              // map response → Contact
 *     "id": "id",
 *     "email": "email",
 *     "firstName": "first_name",
 *     "lastName": "last_name",
 *     "company": "company.name"                // dot-notation supported
 *   }
 * }
 *
 * Env-Var fallbacks (used if Settings is empty):
 *   GENERIC_CRM_BASE_URL, GENERIC_CRM_AUTH_HEADER, GENERIC_CRM_AUTH_VALUE
 *
 * Falls back to a Mock-Mode (returns no contacts) if not configured — that way
 * the UI still renders and developers can build out other features first.
 */

import type { CrmAdapter } from './interface';
import type { UserContext } from '@/core/types';
import type { Contact, ContactFilter, ConversionEvent } from '@/adapters/types';
import { getSecret } from '@/core/settings';

interface GenericCrmConfig {
  baseUrl: string;
  authHeader?: string;
  authValue?: string;
  endpoints?: {
    listContacts?: string;
    getContact?: string;
    pollConversions?: string;
    listSegments?: string;
  };
  fieldMap?: Record<string, string>;
}

function loadConfigFromEnv(): GenericCrmConfig | null {
  const baseUrl = process.env.GENERIC_CRM_BASE_URL?.trim();
  if (!baseUrl) return null;
  return {
    baseUrl,
    authHeader: process.env.GENERIC_CRM_AUTH_HEADER || 'Authorization',
    authValue: process.env.GENERIC_CRM_AUTH_VALUE || undefined,
  };
}

/**
 * Read config from Settings DB (decrypted JSON) — fallback to env.
 * Stored via Settings UI under key `crm_adapter_config` and encrypted at rest.
 */
function loadConfig(): GenericCrmConfig | null {
  try {
    const fromDb = getSecret('crm_adapter_config');
    if (fromDb) {
      const parsed = JSON.parse(fromDb) as GenericCrmConfig;
      if (parsed.baseUrl) return parsed;
    }
  } catch {
    // Fall through to env — DB might be unreachable during boot or
    // APP_ENCRYPTION_KEY might be unset in dev. Keep app running.
  }
  return loadConfigFromEnv();
}

function getNested(obj: unknown, path: string): unknown {
  return path.split('.').reduce<unknown>((acc, key) => {
    if (acc && typeof acc === 'object' && key in (acc as Record<string, unknown>)) {
      return (acc as Record<string, unknown>)[key];
    }
    return undefined;
  }, obj);
}

function mapRecord(raw: unknown, fieldMap: Record<string, string> | undefined): Contact | null {
  if (!raw || typeof raw !== 'object') return null;
  const fm = fieldMap ?? {
    id: 'id',
    email: 'email',
    firstName: 'first_name',
    lastName: 'last_name',
    company: 'company',
  };

  const email = String(getNested(raw, fm.email ?? 'email') ?? '').trim();
  if (!email) return null;

  const id = String(getNested(raw, fm.id ?? 'id') ?? email);

  return {
    externalId: id,
    email,
    firstName: getNested(raw, fm.firstName ?? 'first_name') as string | undefined,
    lastName: getNested(raw, fm.lastName ?? 'last_name') as string | undefined,
    company: getNested(raw, fm.company ?? 'company') as string | undefined,
    customData: raw as Record<string, never>,
  };
}

async function apiCall<T>(config: GenericCrmConfig, path: string): Promise<T> {
  const url = path.startsWith('http') ? path : `${config.baseUrl}${path}`;
  const headers: Record<string, string> = { Accept: 'application/json' };
  if (config.authHeader && config.authValue) {
    headers[config.authHeader] = config.authValue;
  }
  const res = await fetch(url, { headers });
  if (!res.ok) {
    throw new Error(`Generic-REST-CRM ${res.status} ${url}`);
  }
  return (await res.json()) as T;
}

function buildUrl(template: string, params: Record<string, string>): string {
  return template.replace(/\{(\w+)\}/g, (_, key) => encodeURIComponent(params[key] ?? ''));
}

export const genericRestAdapter: CrmAdapter = {
  name: 'generic-rest',

  isConfigured() {
    return loadConfig() !== null;
  },

  async listContacts(filter: ContactFilter, _ctx: UserContext): Promise<Contact[]> {
    const config = loadConfig();
    if (!config) return []; // Mock mode

    const endpoint = config.endpoints?.listContacts ?? '/contacts';
    const url = new URL(`${config.baseUrl}${endpoint}`);
    if (filter.limit) url.searchParams.set('limit', String(filter.limit));
    if (filter.offset) url.searchParams.set('offset', String(filter.offset));
    if (filter.segments?.length) url.searchParams.set('segments', filter.segments.join(','));

    const response = await apiCall<{ data?: unknown[] } | unknown[]>(
      config,
      url.toString(),
    );

    const list = Array.isArray(response)
      ? response
      : Array.isArray(response.data)
        ? response.data
        : [];

    return list
      .map((r) => mapRecord(r, config.fieldMap))
      .filter((c): c is Contact => c !== null);
  },

  async getContactById(id: string, _ctx: UserContext): Promise<Contact | null> {
    const config = loadConfig();
    if (!config) return null;

    const template = config.endpoints?.getContact ?? '/contacts/{id}';
    const path = buildUrl(template, { id });
    try {
      const raw = await apiCall<unknown>(config, path);
      return mapRecord(raw, config.fieldMap);
    } catch (err) {
      if (err instanceof Error && err.message.includes(' 404 ')) return null;
      throw err;
    }
  },

  async pollConversions(since: Date, _ctx: UserContext): Promise<ConversionEvent[]> {
    const config = loadConfig();
    if (!config?.endpoints?.pollConversions) return [];

    const path = buildUrl(config.endpoints.pollConversions, {
      since_iso: since.toISOString(),
    });
    const response = await apiCall<{ data?: unknown[] } | unknown[]>(config, path);
    const list = Array.isArray(response)
      ? response
      : Array.isArray(response.data)
        ? response.data
        : [];

    const events: ConversionEvent[] = [];
    for (const raw of list) {
      if (!raw || typeof raw !== 'object') continue;
      const r = raw as Record<string, unknown>;
      const contactId = String(r.contact_id ?? r.contactId ?? '');
      if (!contactId) continue;
      const occurred = r.occurred_at ?? r.occurredAt ?? r.created_at;
      const event: ConversionEvent = {
        contactId,
        occurredAt: occurred ? new Date(String(occurred)) : new Date(),
      };
      if (typeof r.amount === 'number') event.amount = r.amount;
      if (typeof r.currency === 'string') event.currency = r.currency;
      if (typeof r.external_ref === 'string') event.externalRef = r.external_ref;
      events.push(event);
    }
    return events;
  },

  async listSegments(_ctx: UserContext) {
    const config = loadConfig();
    if (!config?.endpoints?.listSegments) return [];
    const response = await apiCall<{ data?: unknown[] } | unknown[]>(
      config,
      config.endpoints.listSegments,
    );
    const list = Array.isArray(response)
      ? response
      : Array.isArray(response.data)
        ? response.data
        : [];
    const segments: Array<{ id: string; label: string; count?: number }> = [];
    for (const raw of list) {
      if (!raw || typeof raw !== 'object') continue;
      const r = raw as Record<string, unknown>;
      const id = String(r.id ?? '');
      if (!id) continue;
      const seg: { id: string; label: string; count?: number } = {
        id,
        label: String(r.label ?? r.name ?? id),
      };
      if (typeof r.count === 'number') seg.count = r.count;
      segments.push(seg);
    }
    return segments;
  },
};
