import { MerchantContact } from '@/types';
import { getSupabaseClient } from '@/lib/supabase';

type ContactDetection = Array<{
  type?: string;
  value?: string;
  source?: string;
  context?: string;
}> | null;

type FinalResultProductRow = {
  id: string;
  batch_id: string;
  material_id: string;
  offer_url: string;
  merchant_name: string;
  merchant_url: string;
  contact_page_url: string;
  detected_contacts: ContactDetection;
  is_placeholder?: boolean;
  created_at: string;
};

type StandardizedNameRow = {
  material_id: string;
  standardized_name: string;
};

type QueryError = {
  code?: string | null;
  message: string;
};

type RankedMerchantContact = MerchantContact & {
  _priority: number;
};

const MOBILE_REGEX = /^1\d{10}$/;
const FINAL_RESULT_PRIORITY = 1;
const MERCHANT_CONTACT_PRIORITY = 2;

function normalizeText(value: string | null | undefined) {
  return value?.trim() ?? '';
}

function latestTimestamp(left: string, right: string) {
  if (!left) return right;
  if (!right) return left;
  return left.localeCompare(right) >= 0 ? left : right;
}

function isMissingRelationError(error: QueryError | null | undefined) {
  if (!error) return false;
  return error.code === 'PGRST205' || /Could not find the table .* in the schema cache/i.test(error.message);
}

function findDetectedContactValue(contacts: ContactDetection, allowedTypes: readonly string[]) {
  const normalizedTypes = new Set(allowedTypes.map((type) => type.toLowerCase()));

  for (const contact of contacts ?? []) {
    const type = normalizeText(contact?.type).toLowerCase();
    const value = normalizeText(contact?.value);

    if (type && value && normalizedTypes.has(type)) {
      return value;
    }
  }

  return '';
}

function buildMerchantKey(contact: Pick<MerchantContact, 'id' | 'merchant_url' | 'contact_page_url' | 'merchant_name' | 'source_offer_url'>) {
  return normalizeText(contact.merchant_url)
    || normalizeText(contact.contact_page_url)
    || normalizeText(contact.merchant_name)
    || normalizeText(contact.source_offer_url)
    || contact.id;
}

function mergeContacts(existing: RankedMerchantContact, incoming: RankedMerchantContact): RankedMerchantContact {
  const incomingWins = incoming._priority > existing._priority;
  const preferred = incomingWins ? incoming : existing;
  const fallback = incomingWins ? existing : incoming;

  return {
    id: preferred.id || fallback.id,
    merchant_url: preferred.merchant_url || fallback.merchant_url,
    merchant_name: preferred.merchant_name || fallback.merchant_name,
    contact_page_url: preferred.contact_page_url || fallback.contact_page_url,
    standardized_name: preferred.standardized_name || fallback.standardized_name,
    contact_person: preferred.contact_person || fallback.contact_person,
    phone: preferred.phone || fallback.phone,
    mobile: preferred.mobile || fallback.mobile,
    address: preferred.address || fallback.address,
    source_batch_id: preferred.source_batch_id || fallback.source_batch_id,
    source_offer_url: preferred.source_offer_url || fallback.source_offer_url,
    created_at: preferred.created_at || fallback.created_at,
    updated_at: latestTimestamp(existing.updated_at, incoming.updated_at),
    updated_by: preferred.updated_by || fallback.updated_by,
    _priority: Math.max(existing._priority, incoming._priority),
  };
}

function toRankedMerchantContact(row: MerchantContact): RankedMerchantContact {
  const phone = normalizeText(row.phone);

  return {
    id: row.id,
    merchant_url: normalizeText(row.merchant_url),
    merchant_name: normalizeText(row.merchant_name),
    contact_page_url: normalizeText(row.contact_page_url),
    standardized_name: normalizeText(row.standardized_name),
    contact_person: normalizeText(row.contact_person),
    phone,
    mobile: normalizeText(row.mobile) || (MOBILE_REGEX.test(phone) ? phone : ''),
    address: normalizeText(row.address),
    source_batch_id: normalizeText(row.source_batch_id),
    source_offer_url: normalizeText(row.source_offer_url),
    created_at: normalizeText(row.created_at),
    updated_at: normalizeText(row.updated_at) || normalizeText(row.created_at),
    updated_by: normalizeText(row.updated_by) || 'system',
    _priority: MERCHANT_CONTACT_PRIORITY,
  };
}

function fromFinalResultProduct(row: FinalResultProductRow, standardizedNameByMaterialId: Map<string, string>): RankedMerchantContact {
  const phone = findDetectedContactValue(row.detected_contacts, ['phone']);
  const standardizedName = standardizedNameByMaterialId.get(row.material_id) || '';

  return {
    id: row.id,
    merchant_url: normalizeText(row.merchant_url),
    merchant_name: normalizeText(row.merchant_name),
    contact_page_url: normalizeText(row.contact_page_url),
    standardized_name: standardizedName,
    contact_person: findDetectedContactValue(row.detected_contacts, ['contact_person', 'person', 'name']),
    phone,
    mobile: findDetectedContactValue(row.detected_contacts, ['mobile']) || (MOBILE_REGEX.test(phone) ? phone : ''),
    address: findDetectedContactValue(row.detected_contacts, ['address']),
    source_batch_id: normalizeText(row.batch_id),
    source_offer_url: normalizeText(row.offer_url),
    created_at: normalizeText(row.created_at),
    updated_at: normalizeText(row.created_at),
    updated_by: 'system',
    _priority: FINAL_RESULT_PRIORITY,
  };
}

function mergeMerchantContacts(contacts: RankedMerchantContact[]) {
  const mergedByKey = new Map<string, RankedMerchantContact>();

  for (const contact of contacts) {
    const key = buildMerchantKey(contact);
    if (!key) {
      continue;
    }

    const existing = mergedByKey.get(key);
    mergedByKey.set(key, existing ? mergeContacts(existing, contact) : contact);
  }

  return [...mergedByKey.values()]
    .sort((left, right) => right.updated_at.localeCompare(left.updated_at))
    .map(({ _priority, ...contact }) => contact);
}

function matchesSearch(contact: MerchantContact, rawSearch: string) {
  const needle = rawSearch.trim().toLowerCase();

  if (!needle) {
    return true;
  }

  return [contact.merchant_name, contact.contact_person, contact.phone, contact.mobile]
    .some((value) => normalizeText(value).toLowerCase().includes(needle));
}

function hasUsablePhoneContact(contact: Pick<MerchantContact, 'phone' | 'mobile'>) {
  return Boolean(normalizeText(contact.phone) || normalizeText(contact.mobile));
}

async function fetchFinalResultStandardizedNameLookup(
  supabase: ReturnType<typeof getSupabaseClient>,
  rows: FinalResultProductRow[],
) {
  if (rows.length === 0) {
    return new Map<string, string>();
  }

  const materialIds = [...new Set(rows.map((row) => row.material_id).filter(Boolean))];
  const { data, error } = await supabase
    .from('standardized_results')
    .select('material_id, standardized_name')
    .in('material_id', materialIds);

  if (error) {
    throw error;
  }

  return new Map(
    ((data ?? []) as StandardizedNameRow[]).map((row) => [row.material_id, normalizeText(row.standardized_name)]),
  );
}

export async function fetchMerchantContacts(params?: {
  search?: string;
}): Promise<MerchantContact[]> {
  const supabase = getSupabaseClient();

  const [merchantContactsRes, finalResultProductsRes] = await Promise.all([
    supabase
      .from('merchant_contacts')
      .select('*')
      .order('updated_at', { ascending: false }),
    supabase
      .from('final_result_products')
      .select('id, batch_id, material_id, offer_url, merchant_name, merchant_url, contact_page_url, detected_contacts, is_placeholder, created_at')
      .order('created_at', { ascending: false }),
  ]);

  const firstUnexpectedError = [merchantContactsRes.error, finalResultProductsRes.error]
    .find((error) => error && !isMissingRelationError(error));

  if (firstUnexpectedError) {
    throw firstUnexpectedError;
  }

  const finalResultProducts = ((finalResultProductsRes.data ?? []) as FinalResultProductRow[])
    .filter((row) => !row.is_placeholder);
  const standardizedNameByMaterialId = await fetchFinalResultStandardizedNameLookup(supabase, finalResultProducts);

  const mergedContacts = mergeMerchantContacts([
    ...finalResultProducts.map((row) => fromFinalResultProduct(row, standardizedNameByMaterialId)),
    ...((merchantContactsRes.data ?? []) as MerchantContact[]).map(toRankedMerchantContact),
  ]).filter(hasUsablePhoneContact);

  if (params?.search) {
    return mergedContacts.filter((contact) => matchesSearch(contact, params.search ?? ''));
  }

  return mergedContacts;
}
