import { eq, and, ne, count, gte, lte, inArray, type Column, type SQL } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { instructors, lessonTypes, scheduleSlots, enrollments, lessonSessions, instructorBlockedDates, storeClosures, gradingScales, gradingScaleLevels, memberLessonPlans, lessonPlanSections, lessonPlanItems, lessonPlanTemplates, lessonPlanTemplateSections, lessonPlanTemplateItems, lessonPlanItemGradeHistory, lessonSessionPlanItems } from '../db/schema/lessons.js'
import { members } from '../db/schema/accounts.js'
import type {
  InstructorCreateInput,
  InstructorUpdateInput,
  LessonTypeCreateInput,
  LessonTypeUpdateInput,
  ScheduleSlotCreateInput,
  ScheduleSlotUpdateInput,
  EnrollmentCreateInput,
  EnrollmentUpdateInput,
  LessonSessionNotesInput,
  LessonSessionUpdateInput,
  GradingScaleCreateInput,
  GradingScaleUpdateInput,
  LessonPlanCreateInput,
  LessonPlanUpdateInput,
  LessonPlanItemUpdateInput,
  GradeCreateInput,
  SessionPlanItemsInput,
  LessonPlanTemplateCreateInput,
  LessonPlanTemplateUpdateInput,
  TemplateInstantiateInput,
  InstructorBlockedDateCreateInput,
  StoreClosureCreateInput,
  PaginationInput,
} from '@lunarfront/shared/schemas'
import {
  withPagination,
  withSort,
  buildSearchCondition,
  paginatedResponse,
} from '../utils/pagination.js'

export const InstructorService = {
  async create(db: PostgresJsDatabase<any>, input: InstructorCreateInput) {
    const [instructor] = await db
      .insert(instructors)
      .values({
        userId: input.userId,
        displayName: input.displayName,
        bio: input.bio,
        instruments: input.instruments,
      })
      .returning()
    return instructor
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [instructor] = await db
      .select()
      .from(instructors)
      .where(eq(instructors.id, id))
      .limit(1)
    return instructor ?? null
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
    const baseWhere = eq(instructors.isActive, true)
    const searchCondition = params.q
      ? buildSearchCondition(params.q, [instructors.displayName])
      : undefined
    const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere

    const sortableColumns: Record<string, Column> = {
      display_name: instructors.displayName,
      created_at: instructors.createdAt,
    }

    let query = db.select().from(instructors).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, instructors.displayName)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(instructors).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: InstructorUpdateInput) {
    const [instructor] = await db
      .update(instructors)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(instructors.id, id))
      .returning()
    return instructor ?? null
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [instructor] = await db
      .update(instructors)
      .set({ isActive: false, updatedAt: new Date() })
      .where(eq(instructors.id, id))
      .returning()
    return instructor ?? null
  },
}

export const LessonTypeService = {
  async create(db: PostgresJsDatabase<any>, input: LessonTypeCreateInput) {
    const [lessonType] = await db
      .insert(lessonTypes)
      .values({
        name: input.name,
        instrument: input.instrument,
        durationMinutes: input.durationMinutes,
        lessonFormat: input.lessonFormat,
        rateWeekly: input.rateWeekly?.toString(),
        rateMonthly: input.rateMonthly?.toString(),
        rateQuarterly: input.rateQuarterly?.toString(),
      })
      .returning()
    return lessonType
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [lessonType] = await db
      .select()
      .from(lessonTypes)
      .where(eq(lessonTypes.id, id))
      .limit(1)
    return lessonType ?? null
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
    const baseWhere = eq(lessonTypes.isActive, true)
    const searchCondition = params.q
      ? buildSearchCondition(params.q, [lessonTypes.name, lessonTypes.instrument])
      : undefined
    const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere

    const sortableColumns: Record<string, Column> = {
      name: lessonTypes.name,
      instrument: lessonTypes.instrument,
      duration_minutes: lessonTypes.durationMinutes,
      created_at: lessonTypes.createdAt,
    }

    let query = db.select().from(lessonTypes).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, lessonTypes.name)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(lessonTypes).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: LessonTypeUpdateInput) {
    const values: Record<string, unknown> = { ...input, updatedAt: new Date() }
    if (input.rateWeekly !== undefined) values.rateWeekly = input.rateWeekly?.toString() ?? null
    if (input.rateMonthly !== undefined) values.rateMonthly = input.rateMonthly?.toString() ?? null
    if (input.rateQuarterly !== undefined) values.rateQuarterly = input.rateQuarterly?.toString() ?? null

    const [lessonType] = await db
      .update(lessonTypes)
      .set(values)
      .where(eq(lessonTypes.id, id))
      .returning()
    return lessonType ?? null
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [lessonType] = await db
      .update(lessonTypes)
      .set({ isActive: false, updatedAt: new Date() })
      .where(eq(lessonTypes.id, id))
      .returning()
    return lessonType ?? null
  },
}

export const ScheduleSlotService = {
  async create(db: PostgresJsDatabase<any>, input: ScheduleSlotCreateInput) {
    // Check for overlapping slot on same instructor, day, and time
    const [conflict] = await db
      .select({ id: scheduleSlots.id })
      .from(scheduleSlots)
      .where(
        and(
          eq(scheduleSlots.instructorId, input.instructorId),
          eq(scheduleSlots.dayOfWeek, input.dayOfWeek),
          eq(scheduleSlots.startTime, input.startTime),
          eq(scheduleSlots.isActive, true),
        ),
      )
      .limit(1)
    if (conflict) {
      return { error: 'Instructor already has a slot at this day and time' }
    }

    const [slot] = await db
      .insert(scheduleSlots)
      .values({
        instructorId: input.instructorId,
        lessonTypeId: input.lessonTypeId,
        dayOfWeek: input.dayOfWeek,
        startTime: input.startTime,
        room: input.room,
        maxStudents: input.maxStudents,
        rateWeekly: input.rateWeekly?.toString(),
        rateMonthly: input.rateMonthly?.toString(),
        rateQuarterly: input.rateQuarterly?.toString(),
      })
      .returning()
    return slot
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [slot] = await db
      .select()
      .from(scheduleSlots)
      .where(eq(scheduleSlots.id, id))
      .limit(1)
    return slot ?? null
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
    instructorId?: string
    dayOfWeek?: number
  }) {
    const conditions: SQL[] = [eq(scheduleSlots.isActive, true)]

    if (params.q) {
      const search = buildSearchCondition(params.q, [scheduleSlots.room])
      if (search) conditions.push(search)
    }
    if (filters?.instructorId) {
      conditions.push(eq(scheduleSlots.instructorId, filters.instructorId))
    }
    if (filters?.dayOfWeek !== undefined) {
      conditions.push(eq(scheduleSlots.dayOfWeek, filters.dayOfWeek))
    }

    const where = and(...conditions)

    const sortableColumns: Record<string, Column> = {
      day_of_week: scheduleSlots.dayOfWeek,
      start_time: scheduleSlots.startTime,
      room: scheduleSlots.room,
      created_at: scheduleSlots.createdAt,
    }

    let query = db.select().from(scheduleSlots).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, scheduleSlots.dayOfWeek)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(scheduleSlots).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: ScheduleSlotUpdateInput) {
    // If changing day/time/instructor, check for conflicts
    if (input.instructorId || input.dayOfWeek !== undefined || input.startTime) {
      const existing = await ScheduleSlotService.getById(db, id)
      if (!existing) return null

      const checkInstructorId = input.instructorId ?? existing.instructorId
      const checkDay = input.dayOfWeek ?? existing.dayOfWeek
      const checkTime = input.startTime ?? existing.startTime

      const [conflict] = await db
        .select({ id: scheduleSlots.id })
        .from(scheduleSlots)
        .where(
          and(
            eq(scheduleSlots.instructorId, checkInstructorId),
            eq(scheduleSlots.dayOfWeek, checkDay),
            eq(scheduleSlots.startTime, checkTime),
            eq(scheduleSlots.isActive, true),
          ),
        )
        .limit(1)
      if (conflict && conflict.id !== id) {
        return { error: 'Instructor already has a slot at this day and time' }
      }
    }

    const [slot] = await db
      .update(scheduleSlots)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(scheduleSlots.id, id))
      .returning()
    return slot ?? null
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [slot] = await db
      .update(scheduleSlots)
      .set({ isActive: false, updatedAt: new Date() })
      .where(eq(scheduleSlots.id, id))
      .returning()
    return slot ?? null
  },
}

export const EnrollmentService = {
  async create(db: PostgresJsDatabase<any>, input: EnrollmentCreateInput) {
    // Check slot capacity for group lessons
    const slot = await ScheduleSlotService.getById(db, input.scheduleSlotId)
    if (!slot) return { error: 'Schedule slot not found' }

    const [{ activeCount }] = await db
      .select({ activeCount: count() })
      .from(enrollments)
      .where(
        and(
          eq(enrollments.scheduleSlotId, input.scheduleSlotId),
          eq(enrollments.status, 'active'),
        ),
      )
    if (activeCount >= slot.maxStudents) {
      return { error: 'Schedule slot is at capacity' }
    }

    // Check member not already enrolled in a slot at same day/time
    const memberSlots = await db
      .select({
        dayOfWeek: scheduleSlots.dayOfWeek,
        startTime: scheduleSlots.startTime,
      })
      .from(enrollments)
      .innerJoin(scheduleSlots, eq(enrollments.scheduleSlotId, scheduleSlots.id))
      .where(
        and(
          eq(enrollments.memberId, input.memberId),
          eq(enrollments.status, 'active'),
        ),
      )
    const hasConflict = memberSlots.some(
      (s) => s.dayOfWeek === slot.dayOfWeek && s.startTime === slot.startTime,
    )
    if (hasConflict) {
      return { error: 'Member already has a lesson at this day and time' }
    }

    const [enrollment] = await db
      .insert(enrollments)
      .values({
        memberId: input.memberId,
        accountId: input.accountId,
        scheduleSlotId: input.scheduleSlotId,
        instructorId: input.instructorId,
        status: 'active',
        startDate: input.startDate,
        endDate: input.endDate,
        rate: input.rate?.toString(),
        billingInterval: input.billingInterval,
        billingUnit: input.billingUnit,
        notes: input.notes,
      })
      .returning()
    return enrollment
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [enrollment] = await db
      .select()
      .from(enrollments)
      .where(eq(enrollments.id, id))
      .limit(1)
    return enrollment ?? null
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
    memberId?: string
    accountId?: string
    instructorId?: string
    status?: string[]
  }) {
    const conditions: SQL[] = []

    if (filters?.memberId) conditions.push(eq(enrollments.memberId, filters.memberId))
    if (filters?.accountId) conditions.push(eq(enrollments.accountId, filters.accountId))
    if (filters?.instructorId) conditions.push(eq(enrollments.instructorId, filters.instructorId))
    if (filters?.status?.length) {
      conditions.push(inArray(enrollments.status, filters.status as any))
    }

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

    const sortableColumns: Record<string, Column> = {
      start_date: enrollments.startDate,
      status: enrollments.status,
      created_at: enrollments.createdAt,
      member_name: members.firstName,
    }

    let query = db
      .select({
        id: enrollments.id,
        memberId: enrollments.memberId,
        accountId: enrollments.accountId,
        scheduleSlotId: enrollments.scheduleSlotId,
        instructorId: enrollments.instructorId,
        status: enrollments.status,
        startDate: enrollments.startDate,
        endDate: enrollments.endDate,
        rate: enrollments.rate,
        billingInterval: enrollments.billingInterval,
        billingUnit: enrollments.billingUnit,
        makeupCredits: enrollments.makeupCredits,
        notes: enrollments.notes,
        createdAt: enrollments.createdAt,
        updatedAt: enrollments.updatedAt,
        memberName: members.firstName,
        memberLastName: members.lastName,
        instructorName: instructors.displayName,
        slotDayOfWeek: scheduleSlots.dayOfWeek,
        slotStartTime: scheduleSlots.startTime,
        lessonTypeName: lessonTypes.name,
      })
      .from(enrollments)
      .leftJoin(members, eq(enrollments.memberId, members.id))
      .leftJoin(instructors, eq(enrollments.instructorId, instructors.id))
      .leftJoin(scheduleSlots, eq(enrollments.scheduleSlotId, scheduleSlots.id))
      .leftJoin(lessonTypes, eq(scheduleSlots.lessonTypeId, lessonTypes.id))
      .where(where)
      .$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, enrollments.createdAt)
    query = withPagination(query, params.page, params.limit)

    const [rows, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(enrollments).where(where),
    ])

    const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
    const data = rows.map((r) => ({
      ...r,
      memberName: r.memberName && r.memberLastName ? `${r.memberName} ${r.memberLastName}` : undefined,
      slotInfo: r.slotDayOfWeek != null && r.slotStartTime
        ? `${DAYS[r.slotDayOfWeek]} ${r.slotStartTime.slice(0, 5)}`
        : undefined,
    }))

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: EnrollmentUpdateInput) {
    const values: Record<string, unknown> = { ...input, updatedAt: new Date() }
    if (input.rate !== undefined) values.rate = input.rate?.toString() ?? null
    if (input.billingInterval !== undefined) values.billingInterval = input.billingInterval
    if (input.billingUnit !== undefined) values.billingUnit = input.billingUnit

    const [enrollment] = await db
      .update(enrollments)
      .set(values)
      .where(eq(enrollments.id, id))
      .returning()
    return enrollment ?? null
  },

  async updateStatus(db: PostgresJsDatabase<any>, id: string, status: string) {
    const [enrollment] = await db
      .update(enrollments)
      .set({ status: status as any, updatedAt: new Date() })
      .where(eq(enrollments.id, id))
      .returning()
    return enrollment ?? null
  },
}

/**
 * Generate lesson session dates for a given day-of-week within a date range.
 * Returns ISO date strings (YYYY-MM-DD).
 */
function generateDatesForDay(dayOfWeek: number, fromDate: string, toDate: string): string[] {
  const dates: string[] = []
  const start = new Date(fromDate + 'T00:00:00')
  const end = new Date(toDate + 'T00:00:00')

  // Find first occurrence of dayOfWeek on or after start
  const current = new Date(start)
  const diff = (dayOfWeek - current.getDay() + 7) % 7
  current.setDate(current.getDate() + diff)

  while (current <= end) {
    dates.push(current.toISOString().slice(0, 10))
    current.setDate(current.getDate() + 7)
  }
  return dates
}

export const LessonSessionService = {
  /**
   * Generate sessions for an enrollment within a rolling window.
   * Idempotent: skips dates that already have a session.
   * Skips dates blocked by instructor vacation or store closures.
   */
  async generateSessions(db: PostgresJsDatabase<any>, enrollmentId: string, windowWeeks = 4) {
    const enrollment = await EnrollmentService.getById(db, enrollmentId)
    if (!enrollment || enrollment.status !== 'active') return []

    const slot = await ScheduleSlotService.getById(db, enrollment.scheduleSlotId)
    if (!slot) return []

    const today = new Date().toISOString().slice(0, 10)
    const fromDate = enrollment.startDate > today ? enrollment.startDate : today
    const windowEnd = new Date()
    windowEnd.setDate(windowEnd.getDate() + windowWeeks * 7)
    const toDate = enrollment.endDate && enrollment.endDate < windowEnd.toISOString().slice(0, 10)
      ? enrollment.endDate
      : windowEnd.toISOString().slice(0, 10)

    const dates = generateDatesForDay(slot.dayOfWeek, fromDate, toDate)
    if (dates.length === 0) return []

    // Fetch instructor blocked dates and store closures that overlap the window
    const [blockedDates, closures] = await Promise.all([
      db
        .select()
        .from(instructorBlockedDates)
        .where(
          and(
            eq(instructorBlockedDates.instructorId, enrollment.instructorId),
            lte(instructorBlockedDates.startDate, toDate),
            gte(instructorBlockedDates.endDate, fromDate),
          ),
        ),
      db
        .select()
        .from(storeClosures)
        .where(
          and(
            lte(storeClosures.startDate, toDate),
            gte(storeClosures.endDate, fromDate),
          ),
        ),
    ])

    const isBlocked = (date: string): boolean => {
      for (const b of blockedDates) {
        if (date >= b.startDate && date <= b.endDate) return true
      }
      for (const c of closures) {
        if (date >= c.startDate && date <= c.endDate) return true
      }
      return false
    }

    // Find existing sessions to avoid duplicates
    const existing = await db
      .select({ scheduledDate: lessonSessions.scheduledDate })
      .from(lessonSessions)
      .where(
        and(
          eq(lessonSessions.enrollmentId, enrollmentId),
          gte(lessonSessions.scheduledDate, fromDate),
          lte(lessonSessions.scheduledDate, toDate),
        ),
      )
    const existingDates = new Set(existing.map((e) => e.scheduledDate))

    const newDates = dates.filter((d) => !existingDates.has(d) && !isBlocked(d))
    if (newDates.length === 0) return []

    const rows = newDates.map((d) => ({
      enrollmentId,
      scheduledDate: d,
      scheduledTime: slot.startTime,
    }))

    const created = await db.insert(lessonSessions).values(rows).returning()
    return created
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [session] = await db
      .select()
      .from(lessonSessions)
      .where(eq(lessonSessions.id, id))
      .limit(1)
    return session ?? null
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
    enrollmentId?: string
    instructorId?: string
    status?: string[]
    dateFrom?: string
    dateTo?: string
  }) {
    const conditions: SQL[] = []

    if (filters?.enrollmentId) conditions.push(eq(lessonSessions.enrollmentId, filters.enrollmentId))
    if (filters?.instructorId) conditions.push(eq(enrollments.instructorId, filters.instructorId))
    if (filters?.status?.length) conditions.push(inArray(lessonSessions.status, filters.status as any))
    if (filters?.dateFrom) conditions.push(gte(lessonSessions.scheduledDate, filters.dateFrom))
    if (filters?.dateTo) conditions.push(lte(lessonSessions.scheduledDate, filters.dateTo))

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

    const sortableColumns: Record<string, Column> = {
      scheduled_date: lessonSessions.scheduledDate,
      scheduled_time: lessonSessions.scheduledTime,
      status: lessonSessions.status,
      created_at: lessonSessions.createdAt,
    }

    let query = db
      .select({
        id: lessonSessions.id,
        enrollmentId: lessonSessions.enrollmentId,
        scheduledDate: lessonSessions.scheduledDate,
        scheduledTime: lessonSessions.scheduledTime,
        actualStartTime: lessonSessions.actualStartTime,
        actualEndTime: lessonSessions.actualEndTime,
        status: lessonSessions.status,
        instructorNotes: lessonSessions.instructorNotes,
        memberNotes: lessonSessions.memberNotes,
        homeworkAssigned: lessonSessions.homeworkAssigned,
        nextLessonGoals: lessonSessions.nextLessonGoals,
        topicsCovered: lessonSessions.topicsCovered,
        makeupForSessionId: lessonSessions.makeupForSessionId,
        substituteInstructorId: lessonSessions.substituteInstructorId,
        notesCompletedAt: lessonSessions.notesCompletedAt,
        createdAt: lessonSessions.createdAt,
        updatedAt: lessonSessions.updatedAt,
        memberName: members.firstName,
        memberLastName: members.lastName,
        instructorName: instructors.displayName,
        lessonTypeName: lessonTypes.name,
      })
      .from(lessonSessions)
      .leftJoin(enrollments, eq(lessonSessions.enrollmentId, enrollments.id))
      .leftJoin(members, eq(enrollments.memberId, members.id))
      .leftJoin(instructors, eq(enrollments.instructorId, instructors.id))
      .leftJoin(scheduleSlots, eq(enrollments.scheduleSlotId, scheduleSlots.id))
      .leftJoin(lessonTypes, eq(scheduleSlots.lessonTypeId, lessonTypes.id))
      .where(where)
      .$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, lessonSessions.scheduledDate)
    query = withPagination(query, params.page, params.limit)

    const [rows, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() })
        .from(lessonSessions)
        .leftJoin(enrollments, eq(lessonSessions.enrollmentId, enrollments.id))
        .where(where),
    ])

    const data = rows.map((r) => ({
      ...r,
      memberName: r.memberName && r.memberLastName ? `${r.memberName} ${r.memberLastName}` : undefined,
    }))

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: LessonSessionUpdateInput) {
    // Validate substitute instructor availability if provided
    if (input.substituteInstructorId) {
      const session = await LessonSessionService.getById(db, id)
      if (!session) return null

      const sessionDate = session.scheduledDate

      // Check sub is not blocked on this date
      const [blocked] = await db
        .select({ id: instructorBlockedDates.id })
        .from(instructorBlockedDates)
        .where(
          and(
            eq(instructorBlockedDates.instructorId, input.substituteInstructorId),
            lte(instructorBlockedDates.startDate, sessionDate),
            gte(instructorBlockedDates.endDate, sessionDate),
          ),
        )
        .limit(1)
      if (blocked) {
        return { error: 'Substitute instructor is blocked on this date' }
      }

      // Check sub has no conflicting slot at the same day/time
      const enrollment = await EnrollmentService.getById(db, session.enrollmentId)
      if (enrollment) {
        const slot = await ScheduleSlotService.getById(db, enrollment.scheduleSlotId)
        if (slot) {
          const [conflict] = await db
            .select({ id: scheduleSlots.id })
            .from(scheduleSlots)
            .where(
              and(
                eq(scheduleSlots.instructorId, input.substituteInstructorId),
                eq(scheduleSlots.dayOfWeek, slot.dayOfWeek),
                eq(scheduleSlots.startTime, slot.startTime),
                eq(scheduleSlots.isActive, true),
              ),
            )
            .limit(1)
          if (conflict && conflict.id !== slot.id) {
            return { error: 'Substitute instructor has a conflicting slot at this day and time' }
          }
        }
      }
    }

    const [session] = await db
      .update(lessonSessions)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(lessonSessions.id, id))
      .returning()
    return session ?? null
  },

  async updateStatus(db: PostgresJsDatabase<any>, id: string, status: string) {
    const [session] = await db
      .update(lessonSessions)
      .set({ status: status as any, updatedAt: new Date() })
      .where(eq(lessonSessions.id, id))
      .returning()
    return session ?? null
  },

  async updateNotes(db: PostgresJsDatabase<any>, id: string, input: LessonSessionNotesInput) {
    const existing = await LessonSessionService.getById(db, id)
    if (!existing) return null

    const updates: Record<string, unknown> = { ...input, updatedAt: new Date() }
    // Auto-set notesCompletedAt on first notes save
    if (!existing.notesCompletedAt && (input.instructorNotes || input.memberNotes || input.homeworkAssigned)) {
      updates.notesCompletedAt = new Date()
    }

    const [session] = await db
      .update(lessonSessions)
      .set(updates)
      .where(eq(lessonSessions.id, id))
      .returning()
    return session ?? null
  },
}

export const GradingScaleService = {
  async create(db: PostgresJsDatabase<any>, input: GradingScaleCreateInput, createdBy?: string) {
    // If setting as default, unset any existing default
    if (input.isDefault) {
      await db
        .update(gradingScales)
        .set({ isDefault: false, updatedAt: new Date() })
        .where(eq(gradingScales.isDefault, true))
    }

    const [scale] = await db
      .insert(gradingScales)
      .values({
        name: input.name,
        description: input.description,
        isDefault: input.isDefault,
        createdBy,
      })
      .returning()

    const levels = await db
      .insert(gradingScaleLevels)
      .values(
        input.levels.map((l) => ({
          gradingScaleId: scale.id,
          value: l.value,
          label: l.label,
          numericValue: l.numericValue,
          colorHex: l.colorHex,
          sortOrder: l.sortOrder,
        })),
      )
      .returning()

    return { ...scale, levels }
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [scale] = await db
      .select()
      .from(gradingScales)
      .where(eq(gradingScales.id, id))
      .limit(1)
    if (!scale) return null

    const levels = await db
      .select()
      .from(gradingScaleLevels)
      .where(eq(gradingScaleLevels.gradingScaleId, id))
      .orderBy(gradingScaleLevels.sortOrder)

    return { ...scale, levels }
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
    const baseWhere = eq(gradingScales.isActive, true)
    const searchCondition = params.q
      ? buildSearchCondition(params.q, [gradingScales.name])
      : undefined
    const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere

    const sortableColumns: Record<string, Column> = {
      name: gradingScales.name,
      created_at: gradingScales.createdAt,
    }

    let query = db.select().from(gradingScales).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, gradingScales.name)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(gradingScales).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async listAll(db: PostgresJsDatabase<any>) {
    const scales = await db
      .select()
      .from(gradingScales)
      .where(eq(gradingScales.isActive, true))
      .orderBy(gradingScales.name)

    // Fetch levels for all scales in one query
    if (scales.length === 0) return []
    const allLevels = await db
      .select()
      .from(gradingScaleLevels)
      .where(inArray(gradingScaleLevels.gradingScaleId, scales.map((s) => s.id)))
      .orderBy(gradingScaleLevels.sortOrder)

    const levelsByScale = new Map<string, typeof allLevels>()
    for (const level of allLevels) {
      const existing = levelsByScale.get(level.gradingScaleId) ?? []
      existing.push(level)
      levelsByScale.set(level.gradingScaleId, existing)
    }

    return scales.map((s) => ({ ...s, levels: levelsByScale.get(s.id) ?? [] }))
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: GradingScaleUpdateInput) {
    if (input.isDefault) {
      await db
        .update(gradingScales)
        .set({ isDefault: false, updatedAt: new Date() })
        .where(and(eq(gradingScales.isDefault, true), ne(gradingScales.id, id)))
    }

    const [scale] = await db
      .update(gradingScales)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(gradingScales.id, id))
      .returning()
    return scale ?? null
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [scale] = await db
      .update(gradingScales)
      .set({ isActive: false, updatedAt: new Date() })
      .where(eq(gradingScales.id, id))
      .returning()
    return scale ?? null
  },
}

export const LessonPlanService = {
  async create(db: PostgresJsDatabase<any>, input: LessonPlanCreateInput, createdBy?: string) {
    // Deactivate any existing active plan on this enrollment
    await db
      .update(memberLessonPlans)
      .set({ isActive: false, updatedAt: new Date() })
      .where(
        and(
          eq(memberLessonPlans.enrollmentId, input.enrollmentId),
          eq(memberLessonPlans.isActive, true),
        ),
      )

    const [plan] = await db
      .insert(memberLessonPlans)
      .values({
        memberId: input.memberId,
        enrollmentId: input.enrollmentId,
        createdBy,
        title: input.title,
        description: input.description,
        isActive: true,
        startedDate: input.startedDate,
      })
      .returning()

    const sections: any[] = []
    for (const sectionInput of input.sections ?? []) {
      const [section] = await db
        .insert(lessonPlanSections)
        .values({
          lessonPlanId: plan.id,
          title: sectionInput.title,
          description: sectionInput.description,
          sortOrder: sectionInput.sortOrder,
        })
        .returning()

      const items: any[] = []
      if (sectionInput.items?.length) {
        const createdItems = await db
          .insert(lessonPlanItems)
          .values(
            sectionInput.items.map((item) => ({
              sectionId: section.id,
              title: item.title,
              description: item.description,
              gradingScaleId: item.gradingScaleId,
              targetGradeValue: item.targetGradeValue,
              sortOrder: item.sortOrder,
            })),
          )
          .returning()
        items.push(...createdItems)
      }
      sections.push({ ...section, items })
    }

    return { ...plan, sections }
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [plan] = await db
      .select()
      .from(memberLessonPlans)
      .where(eq(memberLessonPlans.id, id))
      .limit(1)
    if (!plan) return null

    const sections = await db
      .select()
      .from(lessonPlanSections)
      .where(eq(lessonPlanSections.lessonPlanId, id))
      .orderBy(lessonPlanSections.sortOrder)

    const sectionIds = sections.map((s) => s.id)
    const items = sectionIds.length > 0
      ? await db
          .select()
          .from(lessonPlanItems)
          .where(inArray(lessonPlanItems.sectionId, sectionIds))
          .orderBy(lessonPlanItems.sortOrder)
      : []

    const itemsBySection = new Map<string, typeof items>()
    for (const item of items) {
      const existing = itemsBySection.get(item.sectionId) ?? []
      existing.push(item)
      itemsBySection.set(item.sectionId, existing)
    }

    // Calculate progress
    const totalItems = items.filter((i) => i.status !== 'skipped').length
    const masteredItems = items.filter((i) => i.status === 'mastered').length
    const progress = totalItems > 0 ? Math.round((masteredItems / totalItems) * 100) : 0

    return {
      ...plan,
      progress,
      sections: sections.map((s) => ({
        ...s,
        items: itemsBySection.get(s.id) ?? [],
      })),
    }
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
    enrollmentId?: string
    memberId?: string
    isActive?: boolean
  }) {
    const conditions: SQL[] = []
    if (filters?.enrollmentId) conditions.push(eq(memberLessonPlans.enrollmentId, filters.enrollmentId))
    if (filters?.memberId) conditions.push(eq(memberLessonPlans.memberId, filters.memberId))
    if (filters?.isActive !== undefined) conditions.push(eq(memberLessonPlans.isActive, filters.isActive))

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

    const sortableColumns: Record<string, Column> = {
      title: memberLessonPlans.title,
      created_at: memberLessonPlans.createdAt,
    }

    let query = db.select().from(memberLessonPlans).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, memberLessonPlans.createdAt)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(memberLessonPlans).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: LessonPlanUpdateInput) {
    // If activating, deactivate others on same enrollment
    if (input.isActive === true) {
      const existing = await LessonPlanService.getById(db, id)
      if (existing) {
        await db
          .update(memberLessonPlans)
          .set({ isActive: false, updatedAt: new Date() })
          .where(
            and(
              eq(memberLessonPlans.enrollmentId, existing.enrollmentId),
              eq(memberLessonPlans.isActive, true),
              ne(memberLessonPlans.id, id),
            ),
          )
      }
    }

    const [plan] = await db
      .update(memberLessonPlans)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(memberLessonPlans.id, id))
      .returning()
    return plan ?? null
  },
}

export const LessonPlanTemplateService = {
  async create(db: PostgresJsDatabase<any>, input: LessonPlanTemplateCreateInput, createdBy?: string) {
    const [template] = await db
      .insert(lessonPlanTemplates)
      .values({
        name: input.name,
        description: input.description,
        instrument: input.instrument,
        skillLevel: input.skillLevel,
        createdBy,
      })
      .returning()

    const sections: any[] = []
    for (const sectionInput of input.sections ?? []) {
      const [section] = await db
        .insert(lessonPlanTemplateSections)
        .values({
          templateId: template.id,
          title: sectionInput.title,
          description: sectionInput.description,
          sortOrder: sectionInput.sortOrder,
        })
        .returning()

      const items: any[] = []
      if (sectionInput.items?.length) {
        const createdItems = await db
          .insert(lessonPlanTemplateItems)
          .values(
            sectionInput.items.map((item) => ({
              sectionId: section.id,
              title: item.title,
              description: item.description,
              gradingScaleId: item.gradingScaleId,
              targetGradeValue: item.targetGradeValue,
              sortOrder: item.sortOrder,
            })),
          )
          .returning()
        items.push(...createdItems)
      }
      sections.push({ ...section, items })
    }

    return { ...template, sections }
  },

  async getById(db: PostgresJsDatabase<any>, id: string) {
    const [template] = await db
      .select()
      .from(lessonPlanTemplates)
      .where(eq(lessonPlanTemplates.id, id))
      .limit(1)
    if (!template) return null

    const sections = await db
      .select()
      .from(lessonPlanTemplateSections)
      .where(eq(lessonPlanTemplateSections.templateId, id))
      .orderBy(lessonPlanTemplateSections.sortOrder)

    const sectionIds = sections.map((s) => s.id)
    const items = sectionIds.length > 0
      ? await db
          .select()
          .from(lessonPlanTemplateItems)
          .where(inArray(lessonPlanTemplateItems.sectionId, sectionIds))
          .orderBy(lessonPlanTemplateItems.sortOrder)
      : []

    const itemsBySection = new Map<string, typeof items>()
    for (const item of items) {
      const existing = itemsBySection.get(item.sectionId) ?? []
      existing.push(item)
      itemsBySection.set(item.sectionId, existing)
    }

    return {
      ...template,
      sections: sections.map((s) => ({
        ...s,
        items: itemsBySection.get(s.id) ?? [],
      })),
    }
  },

  async list(db: PostgresJsDatabase<any>, params: PaginationInput, filters?: {
    instrument?: string
    skillLevel?: string
  }) {
    const conditions: SQL[] = [eq(lessonPlanTemplates.isActive, true)]

    if (params.q) {
      const search = buildSearchCondition(params.q, [lessonPlanTemplates.name, lessonPlanTemplates.instrument])
      if (search) conditions.push(search)
    }
    if (filters?.instrument) {
      conditions.push(eq(lessonPlanTemplates.instrument, filters.instrument))
    }
    if (filters?.skillLevel) {
      conditions.push(eq(lessonPlanTemplates.skillLevel, filters.skillLevel as any))
    }

    const where = and(...conditions)

    const sortableColumns: Record<string, Column> = {
      name: lessonPlanTemplates.name,
      instrument: lessonPlanTemplates.instrument,
      skill_level: lessonPlanTemplates.skillLevel,
      created_at: lessonPlanTemplates.createdAt,
    }

    let query = db.select().from(lessonPlanTemplates).where(where).$dynamic()
    query = withSort(query, params.sort, params.order, sortableColumns, lessonPlanTemplates.name)
    query = withPagination(query, params.page, params.limit)

    const [data, [{ total }]] = await Promise.all([
      query,
      db.select({ total: count() }).from(lessonPlanTemplates).where(where),
    ])

    return paginatedResponse(data, total, params.page, params.limit)
  },

  async update(db: PostgresJsDatabase<any>, id: string, input: LessonPlanTemplateUpdateInput) {
    const [template] = await db
      .update(lessonPlanTemplates)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(lessonPlanTemplates.id, id))
      .returning()
    return template ?? null
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [template] = await db
      .update(lessonPlanTemplates)
      .set({ isActive: false, updatedAt: new Date() })
      .where(eq(lessonPlanTemplates.id, id))
      .returning()
    return template ?? null
  },

  /**
   * Deep-copy a template into a new member_lesson_plan for the given enrollment.
   * Template changes after this point do not affect the created plan.
   */
  async instantiate(db: PostgresJsDatabase<any>, templateId: string, input: TemplateInstantiateInput, createdBy?: string) {
    const template = await LessonPlanTemplateService.getById(db, templateId)
    if (!template) return null

    // Deactivate any existing active plan on this enrollment
    await db
      .update(memberLessonPlans)
      .set({ isActive: false, updatedAt: new Date() })
      .where(
        and(
          eq(memberLessonPlans.enrollmentId, input.enrollmentId),
          eq(memberLessonPlans.isActive, true),
        ),
      )

    const [plan] = await db
      .insert(memberLessonPlans)
      .values({
        memberId: input.memberId,
        enrollmentId: input.enrollmentId,
        createdBy,
        title: input.title ?? template.name,
        description: template.description,
        isActive: true,
      })
      .returning()

    const sections: any[] = []
    for (const tSection of template.sections) {
      const [section] = await db
        .insert(lessonPlanSections)
        .values({
          lessonPlanId: plan.id,
          title: tSection.title,
          description: tSection.description,
          sortOrder: tSection.sortOrder,
        })
        .returning()

      const items: any[] = []
      if (tSection.items.length > 0) {
        const createdItems = await db
          .insert(lessonPlanItems)
          .values(
            tSection.items.map((item) => ({
              sectionId: section.id,
              title: item.title,
              description: item.description,
              gradingScaleId: item.gradingScaleId,
              targetGradeValue: item.targetGradeValue,
              sortOrder: item.sortOrder,
            })),
          )
          .returning()
        items.push(...createdItems)
      }
      sections.push({ ...section, items })
    }

    return { ...plan, sections }
  },
}

export const GradeHistoryService = {
  async create(db: PostgresJsDatabase<any>, lessonPlanItemId: string, input: GradeCreateInput, gradedBy?: string) {
    // Get the item to check current status and validate grading scale
    const [item] = await db
      .select()
      .from(lessonPlanItems)
      .where(eq(lessonPlanItems.id, lessonPlanItemId))
      .limit(1)
    if (!item) return null

    // Create the grade history record
    const [record] = await db
      .insert(lessonPlanItemGradeHistory)
      .values({
        lessonPlanItemId,
        gradingScaleId: input.gradingScaleId,
        gradeValue: input.gradeValue,
        gradedBy,
        sessionId: input.sessionId,
        notes: input.notes,
      })
      .returning()

    // Update the item's current grade and auto-transition status
    const updates: Record<string, unknown> = {
      currentGradeValue: input.gradeValue,
      updatedAt: new Date(),
    }
    if (input.gradingScaleId) updates.gradingScaleId = input.gradingScaleId
    if (item.status === 'not_started') {
      updates.status = 'in_progress'
      updates.startedDate = new Date().toISOString().slice(0, 10)
    }

    const [updatedItem] = await db
      .update(lessonPlanItems)
      .set(updates)
      .where(eq(lessonPlanItems.id, lessonPlanItemId))
      .returning()

    return { record, item: updatedItem }
  },

  async list(db: PostgresJsDatabase<any>, lessonPlanItemId: string) {
    return db
      .select()
      .from(lessonPlanItemGradeHistory)
      .where(eq(lessonPlanItemGradeHistory.lessonPlanItemId, lessonPlanItemId))
      .orderBy(lessonPlanItemGradeHistory.createdAt)
  },
}

export const SessionPlanItemService = {
  async linkItems(db: PostgresJsDatabase<any>, sessionId: string, input: SessionPlanItemsInput) {
    // Verify session exists
    const [session] = await db
      .select()
      .from(lessonSessions)
      .where(eq(lessonSessions.id, sessionId))
      .limit(1)
    if (!session) return null

    // Find already-linked item ids to avoid duplicates
    const existing = await db
      .select({ lessonPlanItemId: lessonSessionPlanItems.lessonPlanItemId })
      .from(lessonSessionPlanItems)
      .where(eq(lessonSessionPlanItems.sessionId, sessionId))
    const existingIds = new Set(existing.map((r) => r.lessonPlanItemId))

    const newIds = input.lessonPlanItemIds.filter((id) => !existingIds.has(id))
    if (newIds.length === 0) return []

    // Auto-transition any not_started items to in_progress
    if (newIds.length > 0) {
      const today = new Date().toISOString().slice(0, 10)
      await db
        .update(lessonPlanItems)
        .set({ status: 'in_progress', startedDate: today, updatedAt: new Date() })
        .where(
          and(
            inArray(lessonPlanItems.id, newIds),
            eq(lessonPlanItems.status, 'not_started'),
          ),
        )
    }

    const rows = newIds.map((id) => ({ sessionId, lessonPlanItemId: id }))
    const created = await db.insert(lessonSessionPlanItems).values(rows).returning()
    return created
  },

  async listForSession(db: PostgresJsDatabase<any>, sessionId: string) {
    return db
      .select()
      .from(lessonSessionPlanItems)
      .where(eq(lessonSessionPlanItems.sessionId, sessionId))
  },
}

export const InstructorBlockedDateService = {
  async create(db: PostgresJsDatabase<any>, instructorId: string, input: InstructorBlockedDateCreateInput) {
    if (input.startDate > input.endDate) {
      return { error: 'Start date must be on or before end date' }
    }

    const [row] = await db
      .insert(instructorBlockedDates)
      .values({
        instructorId,
        startDate: input.startDate,
        endDate: input.endDate,
        reason: input.reason,
      })
      .returning()
    return row
  },

  async list(db: PostgresJsDatabase<any>, instructorId: string) {
    return db
      .select()
      .from(instructorBlockedDates)
      .where(eq(instructorBlockedDates.instructorId, instructorId))
      .orderBy(instructorBlockedDates.startDate)
  },

  async delete(db: PostgresJsDatabase<any>, id: string, instructorId: string) {
    const [row] = await db
      .delete(instructorBlockedDates)
      .where(
        and(
          eq(instructorBlockedDates.id, id),
          eq(instructorBlockedDates.instructorId, instructorId),
        ),
      )
      .returning()
    return row ?? null
  },
}

export const StoreClosureService = {
  async create(db: PostgresJsDatabase<any>, input: StoreClosureCreateInput) {
    if (input.startDate > input.endDate) {
      return { error: 'Start date must be on or before end date' }
    }

    const [row] = await db
      .insert(storeClosures)
      .values({
        name: input.name,
        startDate: input.startDate,
        endDate: input.endDate,
      })
      .returning()
    return row
  },

  async list(db: PostgresJsDatabase<any>) {
    return db
      .select()
      .from(storeClosures)
      .orderBy(storeClosures.startDate)
  },

  async delete(db: PostgresJsDatabase<any>, id: string) {
    const [row] = await db
      .delete(storeClosures)
      .where(eq(storeClosures.id, id))
      .returning()
    return row ?? null
  },
}

export const LessonPlanItemService = {
  async update(db: PostgresJsDatabase<any>, id: string, input: LessonPlanItemUpdateInput) {
    const existing = await db
      .select()
      .from(lessonPlanItems)
      .where(eq(lessonPlanItems.id, id))
      .limit(1)
    if (!existing[0]) return null

    const updates: Record<string, unknown> = { ...input, updatedAt: new Date() }
    const today = new Date().toISOString().slice(0, 10)

    // Auto-set dates on status transitions
    if (input.status) {
      if (input.status !== 'not_started' && !existing[0].startedDate) {
        updates.startedDate = today
      }
      if (input.status === 'mastered' && !existing[0].masteredDate) {
        updates.masteredDate = today
      }
      if (input.status !== 'mastered') {
        updates.masteredDate = null
      }
    }

    const [item] = await db
      .update(lessonPlanItems)
      .set(updates)
      .where(eq(lessonPlanItems.id, id))
      .returning()
    return item ?? null
  },
}
