import 'server-only';

import { db } from "@/shared/db";
import { knowledgePoints, questions, questionsToKnowledgePoints } from "@/shared/db/schema";
import { and, count, desc, eq, inArray, sql, type SQL } from "drizzle-orm";
import { cache } from "react";
import { createId } from "@paralleldrive/cuid2";
import { getKnowledgePointOptions as getKnowledgePointOptionsFromTextbooks } from "@/modules/textbooks/data-access";
import type { CreateQuestionInput } from "./schema";
import type { KnowledgePointOption, Question, QuestionType } from "./types";

type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0]

export type UpdateQuestionInput = {
  type: QuestionType
  difficulty: number
  content: unknown
  knowledgePointIds?: string[]
}

export type GetQuestionsParams = {
  q?: string;
  page?: number;
  pageSize?: number;
  ids?: string[];
  knowledgePointId?: string;
  type?: QuestionType;
  difficulty?: number;
};

export const getQuestions = cache(async ({
  q,
  page = 1,
  pageSize = 50,
  ids,
  knowledgePointId,
  type,
  difficulty,
}: GetQuestionsParams = {}) => {
  const offset = (page - 1) * pageSize;

  const conditions: SQL[] = [];

  if (ids && ids.length > 0) {
    conditions.push(inArray(questions.id, ids));
  }

  if (q && q.trim().length > 0) {
    const needle = `%${q.trim().toLowerCase()}%`;
    conditions.push(
      sql`LOWER(CAST(${questions.content} AS CHAR)) LIKE ${needle}`
    );
  }

  if (type) {
    conditions.push(eq(questions.type, type));
  }

  if (difficulty) {
    conditions.push(eq(questions.difficulty, difficulty));
  }

  if (knowledgePointId) {
    const subQuery = db
      .select({ questionId: questionsToKnowledgePoints.questionId })
      .from(questionsToKnowledgePoints)
      .where(eq(questionsToKnowledgePoints.knowledgePointId, knowledgePointId));
    
    conditions.push(inArray(questions.id, subQuery));
  }

  if (!ids || ids.length === 0) {
    conditions.push(sql`${questions.parentId} IS NULL`)
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [totalResult] = await db
    .select({ count: count() })
    .from(questions)
    .where(whereClause);
  
  const total = Number(totalResult?.count ?? 0);

  const rows = await db.query.questions.findMany({
    where: whereClause,
    limit: pageSize,
    offset: offset,
    orderBy: [desc(questions.createdAt)],
    with: {
      knowledgePoints: {
        with: {
          knowledgePoint: true,
        },
      },
      author: {
        columns: {
          id: true,
          name: true,
          image: true,
        },
      },
      children: true, 
    },
  });

  return {
    data: rows.map((row) => {
      const knowledgePoints =
        row.knowledgePoints?.map((rel) => rel.knowledgePoint) ?? [];

      const author = row.author
        ? {
            id: row.author.id,
            name: row.author.name,
            image: row.author.image,
          }
        : null;

      const mapped: Question = {
        id: row.id,
        content: row.content,
        type: row.type,
        difficulty: row.difficulty ?? 1,
        createdAt: row.createdAt,
        updatedAt: row.updatedAt,
        author,
        knowledgePoints: knowledgePoints.map((kp) => ({ id: kp.id, name: kp.name })),
        childrenCount: row.children?.length ?? 0,
      };

      return mapped;
    }),
    meta: {
      page,
      pageSize,
      total,
      totalPages: Math.ceil(total / pageSize),
    },
  };
});

export type QuestionsDashboardStats = {
  questionCount: number
}

export const getQuestionsDashboardStats = cache(async (): Promise<QuestionsDashboardStats> => {
  const [row] = await db.select({ value: count() }).from(questions)
  return { questionCount: Number(row?.value ?? 0) }
})

async function insertQuestionWithRelations(
  tx: Tx,
  input: CreateQuestionInput,
  authorId: string,
  parentId: string | null = null
): Promise<string> {
  const newQuestionId = createId();

  await tx.insert(questions).values({
    id: newQuestionId,
    content: input.content,
    type: input.type,
    difficulty: input.difficulty,
    authorId: authorId,
    parentId: parentId,
  });

  if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
    await tx.insert(questionsToKnowledgePoints).values(
      input.knowledgePointIds.map((kpId) => ({
        questionId: newQuestionId,
        knowledgePointId: kpId,
      }))
    );
  }

  if (input.subQuestions && input.subQuestions.length > 0) {
    for (const subQ of input.subQuestions) {
      await insertQuestionWithRelations(tx, subQ, authorId, newQuestionId);
    }
  }

  return newQuestionId;
}

export async function createQuestionWithRelations(
  input: CreateQuestionInput,
  authorId: string
): Promise<string> {
  return await db.transaction(async (tx) => {
    return await insertQuestionWithRelations(tx, input, authorId, null);
  });
}

export async function updateQuestionById(
  id: string,
  input: UpdateQuestionInput,
  canEditAll: boolean,
  authorId: string
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx
      .update(questions)
      .set({
        type: input.type,
        difficulty: input.difficulty,
        content: input.content,
        updatedAt: new Date(),
      })
      .where(
        canEditAll
          ? eq(questions.id, id)
          : and(eq(questions.id, id), eq(questions.authorId, authorId))
      );

    await tx
      .delete(questionsToKnowledgePoints)
      .where(eq(questionsToKnowledgePoints.questionId, id));

    if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
      await tx.insert(questionsToKnowledgePoints).values(
        input.knowledgePointIds.map((kpId) => ({
          questionId: id,
          knowledgePointId: kpId,
        }))
      );
    }
  });
}

async function deleteQuestionRecursive(tx: Tx, questionId: string): Promise<void> {
  const children = await tx
    .select({ id: questions.id })
    .from(questions)
    .where(eq(questions.parentId, questionId));

  for (const child of children) {
    await deleteQuestionRecursive(tx, child.id);
  }

  await tx.delete(questions).where(eq(questions.id, questionId));
}

export async function deleteQuestionByIdRecursive(
  questionId: string,
  canDeleteAll: boolean,
  authorId: string
): Promise<void> {
  await db.transaction(async (tx) => {
    const q = await tx.query.questions.findFirst({
      where: eq(questions.id, questionId),
    });

    if (!q) {
      throw new Error("Question not found");
    }

    if (!canDeleteAll && q.authorId !== authorId) {
      throw new Error("Unauthorized");
    }

    await deleteQuestionRecursive(tx, questionId);
  });
}

export async function getKnowledgePointOptions(): Promise<KnowledgePointOption[]> {
  // Delegate to textbooks module data-access to avoid direct queries on
  // textbooks/chapters/knowledgePoints tables (owned by textbooks module).
  return await getKnowledgePointOptionsFromTextbooks()
}

// ---------------------------------------------------------------------------
// Cross-module query interfaces — read-only access for other modules
// ---------------------------------------------------------------------------

export type QuestionKnowledgePoint = {
  questionId: string
  knowledgePointId: string
  knowledgePointName: string
}

/** Returns knowledge points associated with the given question ids. */
export const getKnowledgePointsForQuestions = cache(
  async (questionIds: string[]): Promise<Map<string, QuestionKnowledgePoint[]>> => {
    const result = new Map<string, QuestionKnowledgePoint[]>()
    const uniqueIds = Array.from(new Set(questionIds.filter((v): v is string => typeof v === "string" && v.length > 0)))
    if (uniqueIds.length === 0) return result

    const rows = await db
      .select({
        questionId: questionsToKnowledgePoints.questionId,
        knowledgePointId: knowledgePoints.id,
        knowledgePointName: knowledgePoints.name,
      })
      .from(questionsToKnowledgePoints)
      .innerJoin(knowledgePoints, eq(knowledgePoints.id, questionsToKnowledgePoints.knowledgePointId))
      .where(inArray(questionsToKnowledgePoints.questionId, uniqueIds))

    for (const r of rows) {
      const list = result.get(r.questionId) ?? []
      list.push({
        questionId: r.questionId,
        knowledgePointId: r.knowledgePointId,
        knowledgePointName: r.knowledgePointName,
      })
      result.set(r.questionId, list)
    }
    return result
  }
)
