import { Role } from "@prisma/client";
import prisma from "../../prismaClient";

export type SearchType = "user" | "admin" | "todo";

export interface SearchResultItem {
  id: string;
  type: SearchType;
  title: string;
  subtitle?: string;
  description?: string;
  createdAt?: string;
  score: number;
  meta?: Record<string, any>;
}

const roleLabel = (role: string): string => {
  switch (role) {
    case "MANAGER":
      return "Manager";
    case "ADMIN":
      return "Admin";
    case "USER":
      return "User";
    default:
      return role;
  }
};

const riskLabel = (risk: string): string => {
  switch (risk) {
    case "HIGH_RISK":
      return "High Risk";
    case "MODERATE_RISK":
      return "Moderate Risk";
    case "LOW_RISK":
      return "Low Risk";
    case "NO_RISK":
      return "No Risk";
    default:
      return risk;
  }
};

const todoStatusLabel = (status: string): string => {
  switch (status) {
    case "NEW":
      return "New";
    case "IN_PROGRESS":
      return "In Progress";
    case "COMPLETED":
      return "Completed";
    case "ARCHIVED":
      return "Archived";
    default:
      return status;
  }
};

const priorityLabel = (priority: string): string => {
  switch (priority) {
    case "HIGH_PRIORITY":
      return "High Priority";
    case "MODERATE_PRIORITY":
      return "Moderate Priority";
    case "LOW_PRIORITY":
      return "Low Priority";
    default:
      return priority;
  }
};

const tokenize = (query: string) => {
  const q = query.trim().toLowerCase();
  const tokens = q.split(/\s+/).filter(Boolean);
  return { q, tokens, exact: q };
};

const scoreText = (text: string, q: string, tokens: string[]): number => {
  const hay = (text || "").toLowerCase();
  if (!hay) return 0;
  let score = 0;

  if (hay.includes(q)) {
    score += 80;
    if (hay.startsWith(q)) score += 20;
  }

  const distinct = Array.from(new Set(tokens));
  const found = distinct.filter((t) => hay.includes(t)).length;
  if (distinct.length) {
    score += Math.round((found / distinct.length) * 60);
  }

  if (tokens.length >= 2) {
    for (let i = 0; i < tokens.length - 1; i++) {
      const pair = tokens[i] + " " + tokens[i + 1];
      if (hay.includes(pair)) score += 10;
    }
  }

  return score;
};

const clamp = (n: number, min: number, max: number) =>
  Math.max(min, Math.min(max, n));

export class SearchService {
  async quickSearch(
    query: string,
    limitRaw: number,
    requesterRole?: Role,
    requesterId?: string
  ) {
    const q = String(query || "").trim();
    const limit = clamp(parseInt(String(limitRaw || 8), 10) || 8, 1, 20);
    if (!q || q.length < 2) {
      return { results: [] as SearchResultItem[] };
    }

    const { tokens } = tokenize(q);

    // Users visibility rules
    const usersWhere: any = {
      OR: [
        { firstName: { contains: q, mode: "insensitive" } },
        { lastName: { contains: q, mode: "insensitive" } },
        { email: { contains: q, mode: "insensitive" } },
        { title: { contains: q, mode: "insensitive" } },
      ],
    };

    if (requesterRole === Role.ADMIN) {
      // Admins: can search ONLY normal users (no managers/admins)
      usersWhere.AND = [{ role: Role.USER }];
    } else if (requesterRole === Role.MANAGER) {
      // Managers: can search admins, managers, and normal users
      usersWhere.AND = [{ role: { in: [Role.ADMIN, Role.MANAGER, Role.USER] } }];
    }

    const usersCandidates = await prisma.user.findMany({
      where: usersWhere,
      select: {
        id: true,
        firstName: true,
        lastName: true,
        email: true,
        title: true,
        role: true,
        riskStatus: true,
      },
      take: 150,
    });

    const todoWhere: any = {
      OR: [
        { title: { contains: q, mode: "insensitive" } },
        { task: { contains: q, mode: "insensitive" } },
      ],
    };

    if (requesterRole === Role.ADMIN && requesterId) {
      todoWhere.AND = [{ assignees: { some: { id: requesterId } } }];
    }

    const todosCandidates = await prisma.todo.findMany({
      where: todoWhere,
      select: {
        id: true,
        title: true,
        task: true,
        status: true,
        priority: true,
        createdAt: true,
        dueDate: true,
      },
      orderBy: { createdAt: "desc" },
      take: 150,
    });

    const userResults: SearchResultItem[] = usersCandidates.map((u) => {
      const fullName = `${u.firstName} ${u.lastName}`.trim();
      const titleScore =
        scoreText(fullName, q, tokens) + scoreText(u.email, q, tokens);
      const descScore = scoreText(u.title || "", q, tokens);
      const base = titleScore + descScore;
      const type: SearchType =
        u.role === "ADMIN" || u.role === "MANAGER" ? "admin" : "user";
      return {
        id: u.id,
        type,
        title: fullName || u.email,
        subtitle: `${u.email} • ${u.title ?? ""}`.trim(),
        description: `Role: ${roleLabel(u.role)} • Risk: ${riskLabel(
          u.riskStatus
        )}`,
        score: base,
      };
    });

    const todoResults: SearchResultItem[] = todosCandidates.map((t) => {
      const base = scoreText(t.title, q, tokens) + scoreText(t.task, q, tokens);
      const createdAt = t.createdAt?.toISOString?.() ?? undefined;
      return {
        id: t.id,
        type: "todo",
        title: t.title,
        subtitle: `Status: ${todoStatusLabel(
          t.status
        )} • Priority: ${priorityLabel(t.priority)}`,
        description: t.task?.slice(0, 160) ?? undefined,
        createdAt,
        score: base,
      };
    });

    const combined = [...userResults, ...todoResults]
      .sort((a, b) => b.score - a.score)
      .slice(0, limit);

    return { results: combined };
  }

  async fullSearch(
    query: string,
    pageRaw: number,
    limitRaw: number,
    requesterRole?: Role,
    requesterId?: string
  ) {
    const q = String(query || "").trim();
    const page = Math.max(parseInt(String(pageRaw || 1), 10) || 1, 1);
    const limit = clamp(parseInt(String(limitRaw || 25), 10) || 25, 1, 100);

    if (!q || q.length < 2) {
      return { results: [] as SearchResultItem[], total: 0, page, limit };
    }

    const { tokens } = tokenize(q);

    // Users visibility rules
    const usersWhereFull: any = {
      OR: [
        { firstName: { contains: q, mode: "insensitive" } },
        { lastName: { contains: q, mode: "insensitive" } },
        { email: { contains: q, mode: "insensitive" } },
        { title: { contains: q, mode: "insensitive" } },
      ],
    };

    if (requesterRole === Role.ADMIN) {
      // Admins: can search ONLY normal users (no managers/admins)
      usersWhereFull.AND = [{ role: Role.USER }];
    } else if (requesterRole === Role.MANAGER) {
      // Managers: can search admins, managers, and normal users
      usersWhereFull.AND = [{ role: { in: [Role.ADMIN, Role.MANAGER, Role.USER] } }];
    }

    const userCandidates = await prisma.user.findMany({
      where: usersWhereFull,
      select: {
        id: true,
        firstName: true,
        lastName: true,
        email: true,
        title: true,
        role: true,
        riskStatus: true,
        isLocked: true,
        imageUrl: true,
      },
      take: 500,
    });

    const todoWhere: any = {
      OR: [
        { title: { contains: q, mode: "insensitive" } },
        { task: { contains: q, mode: "insensitive" } },
      ],
    };
    if (requesterRole === Role.ADMIN && requesterId) {
      todoWhere.AND = [{ assignees: { some: { id: requesterId } } }];
    }

    const todoCandidates = await prisma.todo.findMany({
      where: todoWhere,
      select: {
        id: true,
        title: true,
        task: true,
        status: true,
        priority: true,
        createdAt: true,
        dueDate: true,
        createdBy: { select: { id: true, firstName: true, lastName: true } },
        assignees: { select: { id: true, firstName: true, lastName: true } },
      },
      orderBy: { createdAt: "desc" },
      take: 500,
    });

    const userResults: SearchResultItem[] = userCandidates.map((u) => {
      const fullName = `${u.firstName} ${u.lastName}`.trim();
      const titleScore =
        scoreText(fullName, q, tokens) + scoreText(u.email, q, tokens);
      const descScore = scoreText(u.title || "", q, tokens);
      const base = titleScore + descScore;
      const type: SearchType =
        u.role === "ADMIN" || u.role === "MANAGER" ? "admin" : "user";
      const meta = { imageUrl: u.imageUrl, isLocked: u.isLocked };
      return {
        id: u.id,
        type,
        title: fullName || u.email,
        subtitle: `${u.email} • ${u.title ?? ""}`.trim(),
        description: `Role: ${roleLabel(u.role)} • Risk: ${riskLabel(
          u.riskStatus
        )}`,
        score: base,
        meta,
      };
    });

    const todoResults: SearchResultItem[] = todoCandidates.map((t) => {
      const base = scoreText(t.title, q, tokens) + scoreText(t.task, q, tokens);
      const createdAt = t.createdAt?.toISOString?.() ?? undefined;
      const assignees = t.assignees
        .map((a) => `${a.firstName} ${a.lastName}`.trim())
        .join(", ");
      const createdBy = t.createdBy
        ? `${t.createdBy.firstName} ${t.createdBy.lastName}`.trim()
        : "";
      return {
        id: t.id,
        type: "todo",
        title: t.title,
        subtitle: `Status: ${todoStatusLabel(
          t.status
        )} • Priority: ${priorityLabel(t.priority)} • Due: ${
          t.dueDate?.toISOString?.().slice(0, 10) ?? ""
        }`,
        description: t.task?.slice(0, 240) ?? undefined,
        createdAt,
        score: base,
        meta: { assignees, createdBy },
      };
    });

    const combined = [...userResults, ...todoResults].sort((a, b) => {
      if (b.score !== a.score) return b.score - a.score;
      const aTime = a.createdAt ? Date.parse(a.createdAt) : 0;
      const bTime = b.createdAt ? Date.parse(b.createdAt) : 0;
      return bTime - aTime;
    });

    const total = combined.length;
    const start = (page - 1) * limit;
    const end = start + limit;
    const paged = combined.slice(start, end);

    return { results: paged, total, page, limit };
  }
}

export const searchService = new SearchService();
