import { Injectable, BadRequestException } from "@nestjs/common";
import { EntityType, TaskStatus, ProjectStatus } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import type { BrainQueryDto, BrainContextDto, TaskFilter, EventFilter, ProjectFilter } from "./dto";

export interface BrainQueryResult {
  tasks: {
    id: string;
    title: string;
    description: string | null;
    status: TaskStatus;
    priority: string;
    dueDate: Date | null;
    assignee: { id: string; name: string; email: string } | null;
    project: { id: string; name: string; color: string | null } | null;
  }[];
  events: {
    id: string;
    title: string;
    description: string | null;
    startTime: Date;
    endTime: Date | null;
    allDay: boolean;
    location: string | null;
    project: { id: string; name: string; color: string | null } | null;
  }[];
  projects: {
    id: string;
    name: string;
    description: string | null;
    status: ProjectStatus;
    startDate: Date | null;
    endDate: Date | null;
    color: string | null;
    _count: { tasks: number; events: number };
  }[];
  meta: {
    totalTasks: number;
    totalEvents: number;
    totalProjects: number;
    query?: string;
    filters: {
      tasks?: TaskFilter;
      events?: EventFilter;
      projects?: ProjectFilter;
    };
  };
}

export interface BrainContext {
  timestamp: Date;
  workspace: { id: string; name: string };
  summary: {
    activeTasks: number;
    overdueTasks: number;
    upcomingEvents: number;
    activeProjects: number;
  };
  tasks?: {
    id: string;
    title: string;
    status: TaskStatus;
    priority: string;
    dueDate: Date | null;
    isOverdue: boolean;
  }[];
  events?: {
    id: string;
    title: string;
    startTime: Date;
    endTime: Date | null;
    allDay: boolean;
    location: string | null;
  }[];
  projects?: {
    id: string;
    name: string;
    status: ProjectStatus;
    taskCount: number;
  }[];
}

/** Maximum allowed length for search query strings */
const MAX_SEARCH_LENGTH = 500;
/** Maximum allowed limit for search results per entity type */
const MAX_SEARCH_LIMIT = 100;

/**
 * @description Service for querying and aggregating workspace data for AI/brain operations.
 * Provides unified access to tasks, events, and projects with filtering and search capabilities.
 */
@Injectable()
export class BrainService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * @description Query workspace entities with flexible filtering options.
   * Retrieves tasks, events, and/or projects based on specified criteria.
   * @param queryDto - Query parameters including workspaceId, entity types, filters, and search term
   * @returns Filtered tasks, events, and projects with metadata about the query
   * @throws PrismaClientKnownRequestError if database query fails
   */
  async query(queryDto: BrainQueryDto): Promise<BrainQueryResult> {
    const { workspaceId, entities, search, limit = 20 } = queryDto;
    if (search && search.length > MAX_SEARCH_LENGTH) {
      throw new BadRequestException(
        `Search term must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
      );
    }
    if (queryDto.query && queryDto.query.length > MAX_SEARCH_LENGTH) {
      throw new BadRequestException(
        `Query must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
      );
    }
    const clampedLimit = Math.max(1, Math.min(limit, MAX_SEARCH_LIMIT));
    const includeEntities = entities ?? [EntityType.TASK, EntityType.EVENT, EntityType.PROJECT];
    const includeTasks = includeEntities.includes(EntityType.TASK);
    const includeEvents = includeEntities.includes(EntityType.EVENT);
    const includeProjects = includeEntities.includes(EntityType.PROJECT);

    const [tasks, events, projects] = await Promise.all([
      includeTasks ? this.queryTasks(workspaceId, queryDto.tasks, search, clampedLimit) : [],
      includeEvents ? this.queryEvents(workspaceId, queryDto.events, search, clampedLimit) : [],
      includeProjects
        ? this.queryProjects(workspaceId, queryDto.projects, search, clampedLimit)
        : [],
    ]);

    // Build filters object conditionally for exactOptionalPropertyTypes
    const filters: { tasks?: TaskFilter; events?: EventFilter; projects?: ProjectFilter } = {};
    if (queryDto.tasks !== undefined) {
      filters.tasks = queryDto.tasks;
    }
    if (queryDto.events !== undefined) {
      filters.events = queryDto.events;
    }
    if (queryDto.projects !== undefined) {
      filters.projects = queryDto.projects;
    }

    // Build meta object conditionally for exactOptionalPropertyTypes
    const meta: {
      totalTasks: number;
      totalEvents: number;
      totalProjects: number;
      query?: string;
      filters: { tasks?: TaskFilter; events?: EventFilter; projects?: ProjectFilter };
    } = {
      totalTasks: tasks.length,
      totalEvents: events.length,
      totalProjects: projects.length,
      filters,
    };
    if (queryDto.query !== undefined) {
      meta.query = queryDto.query;
    }

    return {
      tasks,
      events,
      projects,
      meta,
    };
  }

  /**
   * @description Get current workspace context for AI operations.
   * Provides a summary of active tasks, overdue items, upcoming events, and projects.
   * @param contextDto - Context options including workspaceId and which entities to include
   * @returns Workspace context with summary counts and optional detailed entity lists
   * @throws NotFoundError if workspace does not exist
   * @throws PrismaClientKnownRequestError if database query fails
   */
  async getContext(contextDto: BrainContextDto): Promise<BrainContext> {
    const {
      workspaceId,
      includeTasks = true,
      includeEvents = true,
      includeProjects = true,
      eventDays = 7,
    } = contextDto;

    const now = new Date();
    const futureDate = new Date(now);
    futureDate.setDate(futureDate.getDate() + eventDays);

    const workspace = await this.prisma.workspace.findUniqueOrThrow({
      where: { id: workspaceId },
      select: { id: true, name: true },
    });

    const [activeTaskCount, overdueTaskCount, upcomingEventCount, activeProjectCount] =
      await Promise.all([
        this.prisma.task.count({
          where: { workspaceId, status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] } },
        }),
        this.prisma.task.count({
          where: {
            workspaceId,
            status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] },
            dueDate: { lt: now },
          },
        }),
        this.prisma.event.count({
          where: { workspaceId, startTime: { gte: now, lte: futureDate } },
        }),
        this.prisma.project.count({
          where: { workspaceId, status: { in: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE] } },
        }),
      ]);

    const context: BrainContext = {
      timestamp: now,
      workspace,
      summary: {
        activeTasks: activeTaskCount,
        overdueTasks: overdueTaskCount,
        upcomingEvents: upcomingEventCount,
        activeProjects: activeProjectCount,
      },
    };

    if (includeTasks) {
      const tasks = await this.prisma.task.findMany({
        where: { workspaceId, status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] } },
        select: { id: true, title: true, status: true, priority: true, dueDate: true },
        orderBy: [{ priority: "desc" }, { dueDate: "asc" }],
        take: 20,
      });
      context.tasks = tasks.map((task) => ({
        ...task,
        isOverdue: task.dueDate ? task.dueDate < now : false,
      }));
    }

    if (includeEvents) {
      context.events = await this.prisma.event.findMany({
        where: { workspaceId, startTime: { gte: now, lte: futureDate } },
        select: {
          id: true,
          title: true,
          startTime: true,
          endTime: true,
          allDay: true,
          location: true,
        },
        orderBy: { startTime: "asc" },
        take: 20,
      });
    }

    if (includeProjects) {
      const projects = await this.prisma.project.findMany({
        where: { workspaceId, status: { in: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE] } },
        select: { id: true, name: true, status: true, _count: { select: { tasks: true } } },
        orderBy: { updatedAt: "desc" },
        take: 10,
      });
      context.projects = projects.map((p) => ({
        id: p.id,
        name: p.name,
        status: p.status,
        taskCount: p._count.tasks,
      }));
    }

    return context;
  }

  /**
   * @description Search across all workspace entities by text.
   * Performs case-insensitive search on titles, descriptions, and locations.
   * @param workspaceId - The workspace to search within
   * @param searchTerm - Text to search for across all entity types
   * @param limit - Maximum number of results per entity type (default: 20)
   * @returns Matching tasks, events, and projects with metadata
   * @throws PrismaClientKnownRequestError if database query fails
   */
  async search(workspaceId: string, searchTerm: string, limit = 20): Promise<BrainQueryResult> {
    if (searchTerm.length > MAX_SEARCH_LENGTH) {
      throw new BadRequestException(
        `Search term must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
      );
    }
    const clampedLimit = Math.max(1, Math.min(limit, MAX_SEARCH_LIMIT));

    const [tasks, events, projects] = await Promise.all([
      this.queryTasks(workspaceId, undefined, searchTerm, clampedLimit),
      this.queryEvents(workspaceId, undefined, searchTerm, clampedLimit),
      this.queryProjects(workspaceId, undefined, searchTerm, clampedLimit),
    ]);

    return {
      tasks,
      events,
      projects,
      meta: {
        totalTasks: tasks.length,
        totalEvents: events.length,
        totalProjects: projects.length,
        query: searchTerm,
        filters: {},
      },
    };
  }

  private async queryTasks(
    workspaceId: string,
    filter?: TaskFilter,
    search?: string,
    limit = 20
  ): Promise<BrainQueryResult["tasks"]> {
    const where: Record<string, unknown> = { workspaceId };
    const now = new Date();

    if (filter) {
      if (filter.status) {
        where.status = filter.status;
      } else if (filter.statuses && filter.statuses.length > 0) {
        where.status = { in: filter.statuses };
      }
      if (filter.priority) {
        where.priority = filter.priority;
      } else if (filter.priorities && filter.priorities.length > 0) {
        where.priority = { in: filter.priorities };
      }
      if (filter.assigneeId) where.assigneeId = filter.assigneeId;
      if (filter.unassigned) where.assigneeId = null;
      if (filter.projectId) where.projectId = filter.projectId;
      if (filter.dueDateFrom || filter.dueDateTo) {
        where.dueDate = {};
        if (filter.dueDateFrom) (where.dueDate as Record<string, unknown>).gte = filter.dueDateFrom;
        if (filter.dueDateTo) (where.dueDate as Record<string, unknown>).lte = filter.dueDateTo;
      }
      if (filter.overdue) {
        where.dueDate = { lt: now };
        where.status = { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] };
      }
    }

    if (search) {
      where.OR = [
        { title: { contains: search, mode: "insensitive" } },
        { description: { contains: search, mode: "insensitive" } },
      ];
    }

    return this.prisma.task.findMany({
      where,
      select: {
        id: true,
        title: true,
        description: true,
        status: true,
        priority: true,
        dueDate: true,
        assignee: { select: { id: true, name: true, email: true } },
        project: { select: { id: true, name: true, color: true } },
      },
      orderBy: [{ priority: "desc" }, { dueDate: "asc" }, { createdAt: "desc" }],
      take: limit,
    });
  }

  private async queryEvents(
    workspaceId: string,
    filter?: EventFilter,
    search?: string,
    limit = 20
  ): Promise<BrainQueryResult["events"]> {
    const where: Record<string, unknown> = { workspaceId };
    const now = new Date();

    if (filter) {
      if (filter.projectId) where.projectId = filter.projectId;
      if (filter.allDay !== undefined) where.allDay = filter.allDay;
      if (filter.startFrom || filter.startTo) {
        where.startTime = {};
        if (filter.startFrom) (where.startTime as Record<string, unknown>).gte = filter.startFrom;
        if (filter.startTo) (where.startTime as Record<string, unknown>).lte = filter.startTo;
      }
      if (filter.upcoming) where.startTime = { gte: now };
    }

    if (search) {
      where.OR = [
        { title: { contains: search, mode: "insensitive" } },
        { description: { contains: search, mode: "insensitive" } },
        { location: { contains: search, mode: "insensitive" } },
      ];
    }

    return this.prisma.event.findMany({
      where,
      select: {
        id: true,
        title: true,
        description: true,
        startTime: true,
        endTime: true,
        allDay: true,
        location: true,
        project: { select: { id: true, name: true, color: true } },
      },
      orderBy: { startTime: "asc" },
      take: limit,
    });
  }

  private async queryProjects(
    workspaceId: string,
    filter?: ProjectFilter,
    search?: string,
    limit = 20
  ): Promise<BrainQueryResult["projects"]> {
    const where: Record<string, unknown> = { workspaceId };

    if (filter) {
      if (filter.status) {
        where.status = filter.status;
      } else if (filter.statuses && filter.statuses.length > 0) {
        where.status = { in: filter.statuses };
      }
      if (filter.startDateFrom || filter.startDateTo) {
        where.startDate = {};
        if (filter.startDateFrom)
          (where.startDate as Record<string, unknown>).gte = filter.startDateFrom;
        if (filter.startDateTo)
          (where.startDate as Record<string, unknown>).lte = filter.startDateTo;
      }
    }

    if (search) {
      where.OR = [
        { name: { contains: search, mode: "insensitive" } },
        { description: { contains: search, mode: "insensitive" } },
      ];
    }

    return this.prisma.project.findMany({
      where,
      select: {
        id: true,
        name: true,
        description: true,
        status: true,
        startDate: true,
        endDate: true,
        color: true,
        _count: { select: { tasks: true, events: true } },
      },
      orderBy: { updatedAt: "desc" },
      take: limit,
    });
  }
}
