import { and, asc, desc, eq, like } from 'drizzle-orm'
import { z } from 'zod'
import { db } from '../db/client.js'
import { goals } from '../db/schema.js'
import { findAuthenticatedUser } from '../lib/authSession.js'

const goalSchema = z.object({
  title: z.string().trim().min(1).max(80),
  targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  activeFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
  activeUntil: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
  color: z.string().trim().min(4).max(32).optional(),
})

const goalUpdateSchema = z.object({
  title: z.string().trim().min(1).max(80).optional(),
  targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
  activeFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
  activeUntil: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
  color: z.string().trim().min(4).max(32).optional(),
})

const goalQuerySchema = z.object({
  query: z.string().trim().optional(),
})

async function requireAuthenticatedUser(request, reply) {
  const user = await findAuthenticatedUser(request)

  if (!user) {
    reply.code(401).send({
      message: '인증이 필요합니다.',
    })
    return null
  }

  return user
}

function hasGoalRangeOverlap(leftStart, leftEnd, rightStart, rightEnd) {
  return leftStart <= rightEnd && leftEnd >= rightStart
}

async function validateGoalSchedule({
  userId,
  activeFrom,
  activeUntil,
  excludeGoalId = null,
}) {
  if (!activeFrom || !activeUntil) {
    return null
  }

  const existingGoals = await db
    .select()
    .from(goals)
    .where(eq(goals.userId, userId))

  return existingGoals.find((goal) => {
    if (excludeGoalId && goal.id === excludeGoalId) {
      return false
    }

    if (!goal.activeFrom || !goal.activeUntil) {
      return false
    }

    return hasGoalRangeOverlap(activeFrom, activeUntil, goal.activeFrom, goal.activeUntil)
  }) ?? null
}

export async function registerGoalRoutes(app) {
  app.get('/api/goals', async (request, reply) => {
    const user = await requireAuthenticatedUser(request, reply)

    if (!user) {
      return
    }

    const query = goalQuerySchema.safeParse(request.query ?? {})

    if (!query.success) {
      return reply.code(400).send({
        message: '목표 조회 조건이 올바르지 않습니다.',
        issues: query.error.flatten(),
      })
    }

    const filters = [eq(goals.userId, user.id)]

    if (query.data.query) {
      filters.push(like(goals.title, `%${query.data.query}%`))
    }

    const items = await db
      .select()
      .from(goals)
      .where(and(...filters))
      .orderBy(desc(goals.updatedAt), asc(goals.targetDate), asc(goals.id))

    return { goals: items }
  })

  app.post('/api/goals', async (request, reply) => {
    const user = await requireAuthenticatedUser(request, reply)

    if (!user) {
      return
    }

    const payload = goalSchema.safeParse(request.body)

    if (!payload.success) {
      return reply.code(400).send({
        message: '목표 입력값이 올바르지 않습니다.',
        issues: payload.error.flatten(),
      })
    }

    if ((payload.data.activeFrom && !payload.data.activeUntil) || (!payload.data.activeFrom && payload.data.activeUntil)) {
      return reply.code(400).send({
        message: '표시 시작일과 종료일은 함께 입력해 주세요.',
      })
    }

    if (payload.data.activeFrom && payload.data.activeUntil && payload.data.activeFrom > payload.data.activeUntil) {
      return reply.code(400).send({
        message: '표시 종료일은 시작일보다 빠를 수 없습니다.',
      })
    }

    const overlappedGoal = await validateGoalSchedule({
      userId: user.id,
      activeFrom: payload.data.activeFrom ?? null,
      activeUntil: payload.data.activeUntil ?? null,
    })

    if (overlappedGoal) {
      return reply.code(409).send({
        message: `표시 기간이 "${overlappedGoal.title}" 목표와 겹칩니다. D-DAY 기간은 하나만 설정할 수 있습니다.`,
      })
    }

    const now = new Date()

    const [goal] = await db
      .insert(goals)
      .values({
        userId: user.id,
        title: payload.data.title,
        targetDate: payload.data.targetDate,
        activeFrom: payload.data.activeFrom ?? null,
        activeUntil: payload.data.activeUntil ?? null,
        color: payload.data.color ?? '#1c1917',
        createdAt: now,
        updatedAt: now,
      })
      .returning()

    return reply.code(201).send({
      message: '목표가 추가되었습니다.',
      goal,
    })
  })

  app.patch('/api/goals/:goalId', async (request, reply) => {
    const user = await requireAuthenticatedUser(request, reply)

    if (!user) {
      return
    }

    const params = z.object({
      goalId: z.coerce.number().int().positive(),
    }).safeParse(request.params)

    if (!params.success) {
      return reply.code(400).send({
        message: '목표 식별자가 올바르지 않습니다.',
      })
    }

    const payload = goalUpdateSchema.safeParse(request.body)

    if (!payload.success) {
      return reply.code(400).send({
        message: '목표 수정값이 올바르지 않습니다.',
        issues: payload.error.flatten(),
      })
    }

    const [existingGoal] = await db
      .select()
      .from(goals)
      .where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))
      .limit(1)

    if (!existingGoal) {
      return reply.code(404).send({
        message: '목표를 찾을 수 없습니다.',
      })
    }

    const nextActiveFrom = payload.data.activeFrom !== undefined ? payload.data.activeFrom : existingGoal.activeFrom
    const nextActiveUntil = payload.data.activeUntil !== undefined ? payload.data.activeUntil : existingGoal.activeUntil
    if ((nextActiveFrom && !nextActiveUntil) || (!nextActiveFrom && nextActiveUntil)) {
      return reply.code(400).send({
        message: '표시 시작일과 종료일은 함께 입력해 주세요.',
      })
    }

    if (nextActiveFrom && nextActiveUntil && nextActiveFrom > nextActiveUntil) {
      return reply.code(400).send({
        message: '표시 종료일은 시작일보다 빠를 수 없습니다.',
      })
    }

    const overlappedGoal = await validateGoalSchedule({
      userId: user.id,
      activeFrom: nextActiveFrom,
      activeUntil: nextActiveUntil,
      excludeGoalId: existingGoal.id,
    })

    if (overlappedGoal) {
      return reply.code(409).send({
        message: `표시 기간이 "${overlappedGoal.title}" 목표와 겹칩니다. D-DAY 기간은 하나만 설정할 수 있습니다.`,
      })
    }

    const nextValues = {
      updatedAt: new Date(),
    }

    if (payload.data.title !== undefined) {
      nextValues.title = payload.data.title
    }

    if (payload.data.targetDate !== undefined) {
      nextValues.targetDate = payload.data.targetDate
    }

    if (payload.data.activeFrom !== undefined) {
      nextValues.activeFrom = payload.data.activeFrom
    }

    if (payload.data.activeUntil !== undefined) {
      nextValues.activeUntil = payload.data.activeUntil
    }

    if (payload.data.color !== undefined) {
      nextValues.color = payload.data.color
    }

    const [goal] = await db
      .update(goals)
      .set(nextValues)
      .where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))
      .returning()

    return {
      message: '목표가 수정되었습니다.',
      goal,
    }
  })

  app.delete('/api/goals/:goalId', async (request, reply) => {
    const user = await requireAuthenticatedUser(request, reply)

    if (!user) {
      return
    }

    const params = z.object({
      goalId: z.coerce.number().int().positive(),
    }).safeParse(request.params)

    if (!params.success) {
      return reply.code(400).send({
        message: '목표 식별자가 올바르지 않습니다.',
      })
    }

    const [existingGoal] = await db
      .select()
      .from(goals)
      .where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))
      .limit(1)

    if (!existingGoal) {
      return reply.code(404).send({
        message: '목표를 찾을 수 없습니다.',
      })
    }

    await db
      .delete(goals)
      .where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))

    return {
      message: '목표가 삭제되었습니다.',
    }
  })
}
