import { sql } from 'drizzle-orm';

import { db } from '@/lib/db';
import { redis } from '@/lib/redis';

// ─── Types ────────────────────────────────────────────────────────────────────

interface ClientResult {
  id: string;
  fullName: string;
}

interface InterestResult {
  id: string;
  clientName: string;
  berthMooringNumber: string | null;
  pipelineStage: string;
}

interface BerthResult {
  id: string;
  mooringNumber: string;
  area: string | null;
  status: string;
}

interface YachtResult {
  id: string;
  name: string;
  hullNumber: string | null;
  registration: string | null;
}

interface CompanyResult {
  id: string;
  name: string;
  legalName: string | null;
  taxId: string | null;
}

interface SearchResults {
  clients: ClientResult[];
  interests: InterestResult[];
  berths: BerthResult[];
  yachts: YachtResult[];
  companies: CompanyResult[];
}

// ─── Search ───────────────────────────────────────────────────────────────────

export async function search(portId: string, query: string): Promise<SearchResults> {
  const [clientRows, berthRows, interestRows, yachtRows, companyRows] = await Promise.all([
    // Clients: full-text search via tsvector
    db.execute<{ id: string; full_name: string }>(sql`
      SELECT id, full_name
      FROM clients
      WHERE port_id = ${portId}
        AND archived_at IS NULL
        AND to_tsvector('simple', coalesce(full_name, ''))
            @@ plainto_tsquery('simple', ${query})
      ORDER BY ts_rank(
        to_tsvector('simple', coalesce(full_name, '')),
        plainto_tsquery('simple', ${query})
      ) DESC
      LIMIT 10
    `),

    // Berths: trigram similarity on mooring_number
    db.execute<{ id: string; mooring_number: string; area: string | null; status: string }>(sql`
      SELECT id, mooring_number, area, status
      FROM berths
      WHERE port_id = ${portId}
        AND mooring_number % ${query}
      ORDER BY similarity(mooring_number, ${query}) DESC
      LIMIT 10
    `),

    // Interests: JOIN to clients and berths, ILIKE search
    db.execute<{
      id: string;
      full_name: string;
      mooring_number: string | null;
      pipeline_stage: string;
    }>(sql`
      SELECT
        i.id,
        c.full_name,
        b.mooring_number,
        i.pipeline_stage
      FROM interests i
      JOIN clients c ON i.client_id = c.id
      LEFT JOIN berths b ON i.berth_id = b.id
      WHERE i.port_id = ${portId}
        AND i.archived_at IS NULL
        AND (
          c.full_name ILIKE ${'%' + query + '%'}
          OR b.mooring_number ILIKE ${'%' + query + '%'}
        )
      LIMIT 10
    `),

    // Yachts: ILIKE on name, hull_number, registration
    db.execute<{
      id: string;
      name: string;
      hull_number: string | null;
      registration: string | null;
    }>(sql`
      SELECT id, name, hull_number, registration
      FROM yachts
      WHERE port_id = ${portId}
        AND archived_at IS NULL
        AND (
          name ILIKE ${'%' + query + '%'}
          OR hull_number ILIKE ${'%' + query + '%'}
          OR registration ILIKE ${'%' + query + '%'}
        )
      ORDER BY
        CASE
          WHEN name ILIKE ${query + '%'} THEN 1
          WHEN name ILIKE ${'%' + query + '%'} THEN 2
          ELSE 3
        END,
        name
      LIMIT 10
    `),

    // Companies: ILIKE on name, legal_name, tax_id
    db.execute<{
      id: string;
      name: string;
      legal_name: string | null;
      tax_id: string | null;
    }>(sql`
      SELECT id, name, legal_name, tax_id
      FROM companies
      WHERE port_id = ${portId}
        AND archived_at IS NULL
        AND (
          name ILIKE ${'%' + query + '%'}
          OR legal_name ILIKE ${'%' + query + '%'}
          OR tax_id ILIKE ${'%' + query + '%'}
        )
      ORDER BY
        CASE
          WHEN name ILIKE ${query + '%'} THEN 1
          WHEN name ILIKE ${'%' + query + '%'} THEN 2
          ELSE 3
        END,
        name
      LIMIT 10
    `),
  ]);

  return {
    clients: Array.from(clientRows).map((r) => ({
      id: r.id,
      fullName: r.full_name,
    })),
    berths: Array.from(berthRows).map((r) => ({
      id: r.id,
      mooringNumber: r.mooring_number,
      area: r.area ?? null,
      status: r.status,
    })),
    interests: Array.from(interestRows).map((r) => ({
      id: r.id,
      clientName: r.full_name,
      berthMooringNumber: r.mooring_number ?? null,
      pipelineStage: r.pipeline_stage,
    })),
    yachts: Array.from(yachtRows).map((r) => ({
      id: r.id,
      name: r.name,
      hullNumber: r.hull_number ?? null,
      registration: r.registration ?? null,
    })),
    companies: Array.from(companyRows).map((r) => ({
      id: r.id,
      name: r.name,
      legalName: r.legal_name ?? null,
      taxId: r.tax_id ?? null,
    })),
  };
}

// ─── Recent Searches ──────────────────────────────────────────────────────────

const RECENT_SEARCH_TTL = 2592000; // 30 days in seconds
const RECENT_SEARCH_MAX = 10;

function recentSearchKey(userId: string, portId: string): string {
  return `recent-search:${userId}:${portId}`;
}

/**
 * Fire-and-forget — saves a search term to the user's recent searches sorted set.
 */
export function saveRecentSearch(userId: string, portId: string, searchTerm: string): void {
  const key = recentSearchKey(userId, portId);
  redis
    .zadd(key, Date.now(), searchTerm)
    .then(() => redis.zremrangebyrank(key, 0, -(RECENT_SEARCH_MAX + 1)))
    .then(() => redis.expire(key, RECENT_SEARCH_TTL))
    .catch(() => {
      // Intentionally swallowed — recent searches are non-critical
    });
}

/**
 * Returns the user's most recent searches, newest first.
 */
export async function getRecentSearches(userId: string, portId: string): Promise<string[]> {
  const key = recentSearchKey(userId, portId);
  const items = await redis.zrevrange(key, 0, RECENT_SEARCH_MAX - 1);
  return items;
}
