import { aliasedTable } from 'drizzle-orm/alias';
import { and, count, desc, eq, ilike, notInArray, or, sql } from 'drizzle-orm';
import { memberships, onlineLessons, users } from '@/db/schema';
import { canReadAllContent } from '@/features/content-approval/lib/workflow';
import { db } from '@/lib/db';
import {
  normalizeOnlineLessonCategory,
  onlineLessonCategoryValues,
} from '../constants/categories';
import type { OnlineLesson, OnlineLessonFilters, OnlineLessonStatus } from '../api/types';

type OnlineLessonRow = {
  id: number;
  organizationId: string;
  title: string;
  description: string | null;
  category: string | null;
  videoUrl: string | null;
  videoFileUrl: string | null;
  videoFileName: string | null;
  attachmentFileName: string | null;
  attachmentFileUrl: string | null;
  thumbnailUrl: string | null;
  status: OnlineLessonStatus;
  submittedAt: Date | null;
  submittedBy: string | null;
  reviewedAt: Date | null;
  reviewedBy: string | null;
  approvedAt: Date | null;
  approvedBy: string | null;
  isPublished: boolean;
  publishedAt: Date | null;
  publishedBy: string | null;
  reviewComment: string | null;
  createdBy: string;
  createdByName: string | null;
  updatedBy: string | null;
  updatedByName: string | null;
  createdAt: Date;
  updatedAt: Date;
};

type SerializeOnlineLessonOptions = {
  includeInternalReviewData?: boolean;
};

export function serializeOnlineLesson(
  row: OnlineLessonRow,
  { includeInternalReviewData = true }: SerializeOnlineLessonOptions = {}
): OnlineLesson {
  const publicLesson: OnlineLesson = {
    id: row.id,
    organization_id: row.organizationId,
    title: row.title,
    description: row.description,
    category: normalizeOnlineLessonCategory(row.category),
    video_url: row.videoUrl,
    video_file_url: row.videoFileUrl,
    video_file_name: row.videoFileName,
    attachment_url: row.attachmentFileName ? `/api/online-lessons/${row.id}/attachment/view` : null,
    attachment_name: row.attachmentFileName,
    thumbnail_url: row.thumbnailUrl,
    status: row.status,
    is_published: row.isPublished,
    published_at: row.publishedAt ? row.publishedAt.toISOString() : null,
    created_at: row.createdAt.toISOString(),
    updated_at: row.updatedAt.toISOString(),
    video_type: row.videoUrl ? 'url' : row.videoFileUrl ? 'upload' : 'none'
  };

  if (!includeInternalReviewData) {
    return publicLesson;
  }

  return {
    ...publicLesson,
    created_by: row.createdBy,
    created_by_name: row.createdByName,
    updated_by: row.updatedBy,
    updated_by_name: row.updatedByName,
    submitted_at: row.submittedAt ? row.submittedAt.toISOString() : null,
    submitted_by: row.submittedBy,
    reviewed_at: row.reviewedAt ? row.reviewedAt.toISOString() : null,
    reviewed_by: row.reviewedBy,
    approved_at: row.approvedAt ? row.approvedAt.toISOString() : null,
    approved_by: row.approvedBy,
    published_by: row.publishedBy,
    review_comment: row.reviewComment
  };
}

function normalizeText(value?: string | null) {
  const normalized = value?.trim();
  return normalized ? normalized : null;
}

function getBaseSelect() {
  const creatorUsers = aliasedTable(users, 'creator_users');
  const updaterUsers = aliasedTable(users, 'updater_users');

  return {
    creatorUsers,
    updaterUsers,
    select: {
      id: onlineLessons.id,
      organizationId: onlineLessons.organizationId,
      title: onlineLessons.title,
      description: onlineLessons.description,
      category: onlineLessons.category,
      videoUrl: onlineLessons.videoUrl,
      videoFileUrl: onlineLessons.videoFileUrl,
      videoFileName: onlineLessons.videoFileName,
      attachmentFileName: onlineLessons.attachmentFileName,
      attachmentFileUrl: onlineLessons.attachmentFileUrl,
      thumbnailUrl: onlineLessons.thumbnailUrl,
      status: onlineLessons.status,
      submittedAt: onlineLessons.submittedAt,
      submittedBy: onlineLessons.submittedBy,
      reviewedAt: onlineLessons.reviewedAt,
      reviewedBy: onlineLessons.reviewedBy,
      approvedAt: onlineLessons.approvedAt,
      approvedBy: onlineLessons.approvedBy,
      isPublished: onlineLessons.isPublished,
      publishedAt: onlineLessons.publishedAt,
      publishedBy: onlineLessons.publishedBy,
      reviewComment: onlineLessons.reviewComment,
      createdBy: onlineLessons.createdBy,
      createdByName: creatorUsers.name,
      updatedBy: onlineLessons.updatedBy,
      updatedByName: updaterUsers.name,
      createdAt: onlineLessons.createdAt,
      updatedAt: onlineLessons.updatedAt
    }
  };
}

export async function listOnlineLessonsForAccess({
  organizationId,
  permissions,
  filters
}: {
  organizationId: string;
  permissions: readonly string[] | null | undefined;
  filters: OnlineLessonFilters;
}) {
  const page = Number(filters.page ?? 1);
  const limit = Number(filters.limit ?? 12);
  const search = normalizeText(filters.search);
  const canManage = canReadAllContent('online_lesson', permissions);
  const { creatorUsers, updaterUsers, select } = getBaseSelect();

  const clauses = [
    eq(onlineLessons.organizationId, organizationId),
    ...(canManage ? [] : [eq(onlineLessons.isPublished, true)]),
    ...(canManage && filters.status ? [eq(onlineLessons.status, filters.status)] : []),
    ...(filters.category ? [eq(onlineLessons.category, filters.category)] : []),
    ...(search
      ? [
          or(
            ilike(onlineLessons.title, `%${search}%`),
            ilike(onlineLessons.description, `%${search}%`),
            ilike(onlineLessons.category, `%${search}%`)
          )!
        ]
      : [])
  ];

  const where = and(...clauses);
  const offset = (page - 1) * limit;

  const [totalResult] = await db.select({ value: count() }).from(onlineLessons).where(where);
  const rows = await db
    .select(select)
    .from(onlineLessons)
    .leftJoin(creatorUsers, eq(onlineLessons.createdBy, creatorUsers.id))
    .leftJoin(updaterUsers, eq(onlineLessons.updatedBy, updaterUsers.id))
    .where(where)
    .orderBy(desc(onlineLessons.createdAt))
    .limit(limit)
    .offset(offset);

  return {
    offset,
    limit,
    total: totalResult?.value ?? 0,
    categories: [...onlineLessonCategoryValues],
    onlineLessons: rows.map((row) =>
      serializeOnlineLesson(row as OnlineLessonRow, {
        includeInternalReviewData: canManage
      })
    )
  };
}

export async function getOnlineLessonByIdForAccess({
  id,
  organizationId,
  permissions
}: {
  id: number;
  organizationId: string;
  permissions: readonly string[] | null | undefined;
}) {
  const canManage = canReadAllContent('online_lesson', permissions);
  const { creatorUsers, updaterUsers, select } = getBaseSelect();

  const [row] = await db
    .select(select)
    .from(onlineLessons)
    .leftJoin(creatorUsers, eq(onlineLessons.createdBy, creatorUsers.id))
    .leftJoin(updaterUsers, eq(onlineLessons.updatedBy, updaterUsers.id))
    .where(
      and(
        eq(onlineLessons.id, id),
        eq(onlineLessons.organizationId, organizationId),
        ...(canManage ? [] : [eq(onlineLessons.isPublished, true)])
      )
    )
    .limit(1);

  return row ? (row as OnlineLessonRow) : null;
}

const onlineLessonReviewerPermissions = [
  'online_lesson:approve',
  'online_lesson:publish',
  'online_lesson:revision',
  'online_lesson:reject'
] as const;

export async function getOnlineLessonReviewRecipientIds({
  organizationId,
  excludeUserIds = []
}: {
  organizationId: string;
  excludeUserIds?: string[];
}) {
  const permissionClauses = onlineLessonReviewerPermissions.map(
    (permission) => sql<boolean>`${permission} = ANY(${memberships.permissions})`
  );

  const rows = await db
    .selectDistinct({ userId: memberships.userId })
    .from(memberships)
    .innerJoin(users, eq(users.id, memberships.userId))
    .where(
      and(
        eq(memberships.organizationId, organizationId),
        eq(users.isActive, true),
        or(...permissionClauses)!,
        ...(excludeUserIds.length ? [notInArray(memberships.userId, excludeUserIds)] : [])
      )
    );

  return rows.map((row) => row.userId);
}
