import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { createSupabaseClient, createServiceClient } from "../_shared/supabase-client.ts";
import { trySignR2Url } from "../_shared/r2_signer.ts";

interface Profile {
  id: string;
  handle: string;
  display_name: string;
  avatar_url: string | null;
  harmony_tier: string | null;
}

interface SearchUser {
  id: string;
  username: string;
  display_name: string;
  avatar_url: string | null;
  harmony_tier: string;
}

interface SearchTag {
  tag: string;
  count: number;
}

interface SearchPost {
  id: string;
  body: string;
  author_id: string;
  author_handle: string;
  author_display_name: string;
  created_at: string;
  image_url: string | null;
  visibility?: string;
}

function extractHashtags(text: string): string[] {
  const matches = text.match(/#\w+/g) || [];
  return [...new Set(matches.map((t) => t.replace("#", "").toLowerCase()))].filter((t) => t.length > 0);
}

function stripHashtags(text: string): string {
  return text.replace(/#\w+/g, " ").replace(/\s+/g, " ").trim();
}

serve(async (req: Request) => {
  // 1. Handle CORS Preflight
  if (req.method === "OPTIONS") {
    return new Response(null, {
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST",
        "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
      },
    });
  }

  try {
    // 2. Auth & Input Parsing
    const authHeader = req.headers.get("Authorization");
    if (!authHeader) throw new Error("Missing authorization header");

    let query: string | null = null;
    if (req.method === "POST") {
      try {
        const body = await req.json();
        query = body.query;
      } catch { /* ignore parsing error */ }
    }
    if (!query) {
      const url = new URL(req.url);
      query = url.searchParams.get("query");
    }

    // Return empty if no query
    if (!query || query.trim().length === 0) {
      return new Response(
        JSON.stringify({ users: [], tags: [], posts: [] }),
        { status: 200, headers: { "Content-Type": "application/json" } }
      );
    }

    const rawQuery = query.trim();
    const isHashtagSearch = rawQuery.startsWith("#");
    const cleanTag = isHashtagSearch ? rawQuery.replace("#", "").toLowerCase() : "";
    const hashtagFilters = extractHashtags(rawQuery);
    const ftsQuery = stripHashtags(rawQuery).replace(/[|&!]/g, " ").trim();
    const hasFts = ftsQuery.length > 0;
    const safeQuery = rawQuery.toLowerCase().replace(/[,()]/g, "");

    console.log("Search query:", rawQuery, "isHashtagSearch:", isHashtagSearch, "cleanTag:", cleanTag);

    const supabase = createSupabaseClient(authHeader);
    const serviceClient = createServiceClient();

    const { data: { user }, error: authError } = await supabase.auth.getUser();
    if (authError || !user) throw new Error("Unauthorized");

    // 3. Prepare Exclusion Lists (Blocked Users)
    const { data: blockedUsers } = await serviceClient
      .from("blocks")
      .select("blocked_id")
      .eq("blocker_id", user.id);

    const blockedIds = (blockedUsers?.map((b) => b.blocked_id) || []);
    const excludeIds = [...blockedIds, user.id]; // Exclude self from user search
    const postExcludeIds = blockedIds; // Allow own posts in search

    // 4. Parallel Execution (Fastest Approach)
    // Users + tags first so tag matches can inform post search.
    const [usersResult, tagsResult] = await Promise.all([

      // A. Search Users
      (async () => {
        let dbQuery = serviceClient
          .from("profiles")
          .select("id, handle, display_name, avatar_url")
          .or(`handle.ilike.%${safeQuery}%,display_name.ilike.%${safeQuery}%`)
          .limit(5);

        if (excludeIds.length > 0) {
          dbQuery = dbQuery.not("id", "in", `(${excludeIds.join(",")})`);
        }
        return await dbQuery;
      })(),

      // B. Search Tags (Using the View for performance)
      (async () => {
        // NOTE: Ensure you have run the SQL to create 'view_searchable_tags'
        // If view missing, this returns error, handle gracefully
        return await serviceClient
          .from("view_searchable_tags")
          .select("tag, count")
          .ilike("tag", `%${isHashtagSearch ? cleanTag : safeQuery}%`)
          .order("count", { ascending: false })
          .limit(5);
      })(),

    ]);

    const matchedTags = (tagsResult.data || [])
      .map((t: any) => String(t.tag).toLowerCase())
      .filter((t: string) => t.length > 0);

    const tagCandidates = matchedTags.length > 0
      ? matchedTags
      : (isHashtagSearch && cleanTag.length > 0 ? [cleanTag] : []);

    // C. Search Posts (tag-first for hashtag queries; hybrid for others)
    const postsResult = await (async () => {
      const postsMap = new Map<string, any>();

    if (isHashtagSearch) {
      if (cleanTag.length > 0) {
        let exactTagQuery = serviceClient
          .from("posts")
          .select("id, body, tags, created_at, author_id, image_url, visibility, profiles!posts_author_id_fkey(handle, display_name)")
          .contains("tags", [cleanTag])
          .order("created_at", { ascending: false })
          .limit(20);
        if (excludeIds.length > 0) {
          exactTagQuery = exactTagQuery.not("author_id", "in", `(${excludeIds.join(",")})`);
        }
        const exactTagResult = await exactTagQuery;
        (exactTagResult.data || []).forEach((p: any) => postsMap.set(p.id, p));
      }

      if (tagCandidates.length > 0) {
        let tagQuery = serviceClient
          .from("posts")
          .select("id, body, tags, created_at, author_id, image_url, visibility, profiles!posts_author_id_fkey(handle, display_name)")
          .overlaps("tags", tagCandidates)
          .order("created_at", { ascending: false })
          .limit(20);
        if (postExcludeIds.length > 0) {
          tagQuery = tagQuery.not("author_id", "in", `(${postExcludeIds.join(",")})`);
        }
        const tagResult = await tagQuery;
        (tagResult.data || []).forEach((p: any) => postsMap.set(p.id, p));
      }

      return { data: Array.from(postsMap.values()).slice(0, 20), error: null };
    }

      if (hasFts) {
        let ftsDbQuery = serviceClient
          .from("posts")
          .select("id, body, tags, created_at, author_id, image_url, visibility, profiles!posts_author_id_fkey(handle, display_name)")
          .order("created_at", { ascending: false })
          .limit(20);
        if (postExcludeIds.length > 0) {
          ftsDbQuery = ftsDbQuery.not("author_id", "in", `(${postExcludeIds.join(",")})`);
        }
        ftsDbQuery = ftsDbQuery.textSearch("fts", ftsQuery, {
          type: "websearch",
          config: "english",
        });
        const ftsResult = await ftsDbQuery;
        (ftsResult.data || []).forEach((p: any) => postsMap.set(p.id, p));
      }

      if (tagCandidates.length > 0) {
        let tagOverlapQuery = serviceClient
          .from("posts")
          .select("id, body, tags, created_at, author_id, image_url, visibility, profiles!posts_author_id_fkey(handle, display_name)")
          .overlaps("tags", tagCandidates)
          .order("created_at", { ascending: false })
          .limit(20);
        if (postExcludeIds.length > 0) {
          tagOverlapQuery = tagOverlapQuery.not("author_id", "in", `(${postExcludeIds.join(",")})`);
        }
        const tagOverlapResult = await tagOverlapQuery;
        (tagOverlapResult.data || []).forEach((p: any) => postsMap.set(p.id, p));
      }

      if (hashtagFilters.length > 0) {
        let tagOverlapQuery = serviceClient
          .from("posts")
          .select("id, body, tags, created_at, author_id, image_url, visibility, profiles!posts_author_id_fkey(handle, display_name)")
          .overlaps("tags", hashtagFilters)
          .order("created_at", { ascending: false })
          .limit(20);
        if (postExcludeIds.length > 0) {
          tagOverlapQuery = tagOverlapQuery.not("author_id", "in", `(${postExcludeIds.join(",")})`);
        }
        const tagOverlapResult = await tagOverlapQuery;
        (tagOverlapResult.data || []).forEach((p: any) => postsMap.set(p.id, p));
      }

      return { data: Array.from(postsMap.values()).slice(0, 20), error: null };
    })();

    // 5. Process Users (Get Harmony Tiers)
    const profiles = usersResult.data || [];
    let users: SearchUser[] = [];

    if (profiles.length > 0) {
        const { data: trustStates } = await serviceClient
            .from("trust_state")
            .select("user_id, tier")
            .in("user_id", profiles.map(p => p.id));

        const trustMap = new Map(trustStates?.map(t => [t.user_id, t.tier]) || []);

        users = profiles.map((p: any) => ({
            id: p.id,
            username: p.handle,
            display_name: p.display_name || p.handle,
            avatar_url: p.avatar_url,
            harmony_tier: trustMap.get(p.id) || "new",
        }));
    }

    // 6. Process Tags
    const tags: SearchTag[] = (tagsResult.data || []).map((t: any) => ({
        tag: t.tag,
        count: t.count
    }));

    // 7. Process Posts
    const searchPosts: SearchPost[] = await Promise.all(
      (postsResult.data || []).map(async (p: any) => ({
        id: p.id,
        body: p.body,
        author_id: p.author_id,
        author_handle: p.profiles?.handle || "unknown",
        author_display_name: p.profiles?.display_name || "Unknown User",
        created_at: p.created_at,
        image_url: p.image_url ? await trySignR2Url(p.image_url) : null,
        visibility: p.visibility,
      }))
    );

    // 8. Return Result
    return new Response(JSON.stringify({ users, tags, posts: searchPosts }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });

  } catch (error: any) {
    console.error("Search error:", error);
    return new Response(
      JSON.stringify({ error: error.message || "Internal server error" }),
      { status: 500, headers: { "Content-Type": "application/json" } }
    );
  }
});
