import { MeiliSearch } from "meilisearch";
import type { PropertyData } from "./strapi";

// Meilisearch client singleton
let meilisearchClient: MeiliSearch | null = null;

const LISTINGS_INDEX = "listings";

/**
 * Get or create Meilisearch client for frontend (read-only).
 * Returns null when Meilisearch is not configured or inactive (e.g. production without a host),
 * so callers fall back to Strapi without triggering request errors.
 */
const getMeilisearchClient = (): MeiliSearch | null => {
  if (meilisearchClient) {
    return meilisearchClient;
  }

  const host =
    process.env.NEXT_PUBLIC_MEILISEARCH_HOST ||
    process.env.MEILISEARCH_HOST ||
    "http://localhost:7700";
  // Server can use MEILISEARCH_SEARCH_KEY (not exposed to client); client uses NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY
  const searchKey =
    (typeof window === "undefined"
      ? process.env.MEILISEARCH_SEARCH_KEY ||
        process.env.MEILISEARCH_MASTER_KEY ||
        process.env.NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY
      : process.env.NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY) as string | undefined;

  const isProduction =
    typeof process !== "undefined" && process.env.NODE_ENV === "production";
  if (
    !host ||
    (isProduction && (host.includes("localhost") || host.includes("127.0.0.1")))
  ) {
    return null;
  }

  // Require API key for remote Meilisearch (avoids 401 and never cache a keyless client)
  const isRemoteHost =
    host.startsWith("https://") ||
    (host.startsWith("http://") && !host.includes("localhost") && !host.includes("127.0.0.1"));
  if (isRemoteHost && (!searchKey || !String(searchKey).trim())) {
    return null;
  }

  try {
    meilisearchClient = new MeiliSearch({
      host,
      ...(searchKey && String(searchKey).trim() && { apiKey: searchKey.trim() }),
    });
    return meilisearchClient;
  } catch (error) {
    console.warn("Meilisearch client init failed (search will use Strapi):", error);
    return null;
  }
};

/**
 * Transform "listings" index hit to PropertyData (for SearchModal & search UI)
 */
const transformListingHitToProperty = (doc: any): PropertyData => {
  const locationSlug = doc.locationSlug ?? doc.location ?? "";
  const locationName = doc.location ?? doc.locationSlug ?? "";
  const typeSlugs: string[] = Array.isArray(doc.typeSlugs)
    ? doc.typeSlugs.filter((s: unknown) => typeof s === "string")
    : [];
  const typesFromDoc =
    typeSlugs.length > 0 ? typeSlugs.map((s) => ({ slug: s })) : undefined;
  const primaryType =
    typeSlugs[0] ?? doc.category ?? doc.propertyType ?? "residential";
  return {
    id: parseInt(doc.id, 10),
    slug: doc.slug || "",
    title: doc.title || "",
    ...(typesFromDoc && { types: typesFromDoc }),
    propertyType: primaryType,
    propertyStatus: (doc.status ?? doc.propertyStatus) as PropertyData["propertyStatus"],
    statusPriority: typeof doc.statusPriority === "number" ? doc.statusPriority : 0,
    displayOrder: 0,
    address: typeof locationName === "string" ? locationName : "",
    location:
      locationSlug || locationName
        ? {
            id: 0,
            name: typeof locationName === "string" ? locationName : "",
            slug: typeof locationSlug === "string" ? locationSlug : "",
            priority: 0,
          }
        : undefined,
    unitSize: 0,
    description: "",
  };
};

/**
 * Search properties in Meilisearch ("listings" index)
 * Used for auto-suggestions in SearchModal
 */
export const searchProperties = async (
  query: string,
  limit: number = 8
): Promise<PropertyData[]> => {
  const client = getMeilisearchClient();
  if (!client) return [];

  try {
    const index = client.index(LISTINGS_INDEX);
    const response = await index.search(query.trim(), {
      limit,
      attributesToRetrieve: [
        "id",
        "slug",
        "title",
        "category",
        "typeSlugs",
        "status",
        "statusPriority",
        "location",
        "locationSlug",
      ],
      sort: ["title:asc"],
    });

    if (response.hits?.length) {
      return response.hits.map(transformListingHitToProperty);
    }
    return [];
  } catch (error) {
    console.warn(
      "Meilisearch search unavailable (using Strapi fallback):",
      error instanceof Error ? error.message : error
    );
    return [];
  }
};

/**
 * Search properties in Meilisearch with filters and pagination
 * Returns property IDs that match the search criteria
 */
export interface MeilisearchSearchOptions {
  query?: string;
  filters?: {
    propertyType?: string;
    status?: string;
    locationSlug?: string;
  };
  page?: number;
  pageSize?: number;
  sort?: string[];
}

/** Full hit returned by search (listings index); used to render list without Strapi. */
export interface MeilisearchSearchHit extends ListingHit {
  locationSlug?: string | null;
  statusPriority?: number | null;
}

export interface MeilisearchSearchResult {
  hits: MeilisearchSearchHit[];
  total: number;
  page: number;
  pageSize: number;
  pageCount: number;
}

/**
 * Listing hit from Meilisearch "listings" index (id, title, slug, price, area, location, category, thumbnail, status).
 */
export interface ListingHit {
  id: string;
  title: string;
  slug: string;
  price: number | null;
  area: number | string | null;
  location: string | null;
  category: string | null;
  /** Listing page slugs from Property.types (Meilisearch). */
  typeSlugs?: string[] | null;
  thumbnail: string | null;
  status: string | null;
  statusPriority?: number | null;
}

/**
 * Instant search: query the "listings" index and return hits (for instant search UI).
 */
export const searchListings = async (
  query: string,
  limit: number = 10
): Promise<ListingHit[]> => {
  const client = getMeilisearchClient();
  if (!client) return [];

  try {
    const index = client.index(LISTINGS_INDEX);
    const response = await index.search(query.trim(), {
      limit,
      attributesToRetrieve: [
        "id",
        "title",
        "slug",
        "price",
        "area",
        "location",
        "category",
        "thumbnail",
        "status",
        "statusPriority",
      ],
    });

    if (!response.hits?.length) return [];

    return response.hits.map((hit: any) => ({
      id: String(hit.id),
      title: hit.title ?? "",
      slug: hit.slug ?? "",
      price: hit.price ?? null,
      area: hit.area ?? null,
      location: hit.location ?? null,
      category: hit.category ?? null,
      thumbnail: hit.thumbnail ?? null,
      status: hit.status ?? null,
      statusPriority:
        typeof hit.statusPriority === "number" ? hit.statusPriority : null,
    }));
  } catch (error) {
    console.warn(
      "Meilisearch listings search unavailable:",
      error instanceof Error ? error.message : error
    );
    return [];
  }
};

export const searchPropertiesWithFilters = async (
  options: MeilisearchSearchOptions
): Promise<MeilisearchSearchResult | null> => {
  const client = getMeilisearchClient();
  if (!client) {
    if (process.env.NODE_ENV === "development") {
      const host = process.env.NEXT_PUBLIC_MEILISEARCH_HOST;
      const hasKey = Boolean(
        process.env.MEILISEARCH_SEARCH_KEY || process.env.NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY
      );
      console.warn(
        "[Meilisearch] Client null — search API not called. Check NEXT_PUBLIC_MEILISEARCH_HOST and MEILISEARCH_SEARCH_KEY or NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY.",
        { host: host || "(missing)", hasKey }
      );
    }
    return null;
  }

  try {
    const page = options.page || 1;
    const pageSize = options.pageSize || 8;
    const offset = (page - 1) * pageSize;

    const index = client.index(LISTINGS_INDEX);

    // "listings" index uses category, status, locationSlug
    const filterParts: string[] = [];
    if (
      options.filters?.propertyType &&
      options.filters.propertyType !== "all"
    ) {
      const t = options.filters.propertyType.replace(/"/g, '\\"');
      filterParts.push(`(typeSlugs = "${t}" OR category = "${t}")`);
    }
    if (options.filters?.status && options.filters.status !== "all") {
      filterParts.push(`status = "${options.filters.status}"`);
    }
    if (
      options.filters?.locationSlug &&
      options.filters.locationSlug !== "all"
    ) {
      filterParts.push(`locationSlug = "${options.filters.locationSlug}"`);
    }
    const filter =
      filterParts.length > 0 ? filterParts.join(" AND ") : undefined;

    const searchParams: any = {
      limit: pageSize,
      offset,
      attributesToRetrieve: [
        "id",
        "title",
        "slug",
        "category",
        "typeSlugs",
        "thumbnail",
        "status",
        "statusPriority",
        "location",
        "locationSlug",
      ],
      sort:
        options.sort ||
        (options.filters?.status && options.filters.status !== "all"
          ? ["statusPriority:asc", "title:asc"]
          : ["title:asc"]),
    };

    if (filter) searchParams.filter = filter;

    const query = options.query?.trim() || "";
    const response = await index.search(query, searchParams);

    const total = response.estimatedTotalHits || response.totalHits || 0;
    const pageCount = Math.ceil(total / pageSize);

    const hits: MeilisearchSearchHit[] = (response.hits || []).map((hit: any) => ({
      id: String(hit.id),
      title: hit.title ?? "",
      slug: hit.slug ?? "",
      price: hit.price ?? null,
      area: hit.area ?? null,
      location: hit.location ?? null,
      category: hit.category ?? null,
      typeSlugs: Array.isArray(hit.typeSlugs) ? hit.typeSlugs : null,
      thumbnail: hit.thumbnail ?? null,
      status: hit.status ?? null,
      statusPriority:
        typeof hit.statusPriority === "number" ? hit.statusPriority : null,
      locationSlug: hit.locationSlug ?? null,
    }));

    return {
      hits,
      total,
      page,
      pageSize,
      pageCount,
    };
  } catch (error) {
    console.warn(
      "Meilisearch search with filters unavailable (using Strapi fallback):",
      error instanceof Error ? error.message : error
    );
    return null;
  }
};
