import type { NextRequest } from "next/server";
import { z } from "zod";
import { Prisma } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { rateLimit } from "@/lib/rate-limit";
import {
  getCurrentUser,
  getClientIp,
  getUserAgent,
} from "@/lib/current-user";
import { ok, ERR, sanitizeBigInt } from "@/lib/api-response";

type TxClient = Prisma.TransactionClient;

const VoteBody = z.object({
  artistId: z.string().min(1).max(8),
  count: z.number().int().min(1).max(99_999),
});

/** 内部抛错用，事务捕获后转为业务错误响应 */
class QuotaExhaustedError extends Error {
  constructor(public remaining: number) {
    super("QUOTA_EXHAUSTED");
  }
}

/**
 * POST /api/vote
 * 投票接口。
 *
 * 规则：
 *  - 每日总额度：ActivityConfig.dailyQuota（默认 10）
 *  - 无单艺人上限（perArtistLimit 字段保留但不强制）
 *  - 单用户限流：1 秒 5 次；单 IP 限流：60 秒 60 次
 *
 * 流程：
 *  1. 鉴权 + 反作弊限流
 *  2. 校验活动开关 + 时间窗
 *  3. 事务：当日额度检查 → 写投票 → 累加艺人票数 → 更新应援 → 扣减额度
 *  4. 返回最新票数 + 当日剩余
 */
export async function POST(req: NextRequest) {
  try {
    const user = await getCurrentUser();
    if (!user) return ERR.UNAUTHORIZED();

    const userRl = await rateLimit(`vote:user:${user.id}`, 1, 5);
    if (!userRl.allowed) return ERR.RATE_LIMITED();

    const ip = await getClientIp();
    if (ip) {
      const ipRl = await rateLimit(`vote:ip:${ip}`, 60, 60);
      if (!ipRl.allowed) return ERR.RATE_LIMITED();
    }

    const raw = await req.json();
    const parsed = VoteBody.safeParse(raw);
    if (!parsed.success) {
      return ERR.VALIDATION(parsed.error.issues[0]?.message ?? "参数错误");
    }
    const { artistId, count } = parsed.data;

    const config = await prisma.activityConfig.findUnique({ where: { id: 1 } });
    if (!config?.voteEnabled) return ERR.ACTIVITY_OFF();
    const now = new Date();
    if (now < config.startAt || now > config.endAt) return ERR.ACTIVITY_OFF();

    const ua = await getUserAgent();
    const today = startOfDay();
    const dailyQuota = config.dailyQuota;

    try {
      const result = await prisma.$transaction(async (tx: TxClient) => {
        // 1. 当日额度（不存在则按 config 创建）
        const dq = await tx.dailyQuota.upsert({
          where: { userId_date: { userId: user.id, date: today } },
          create: {
            userId: user.id,
            date: today,
            totalQuota: dailyQuota,
            usedQuota: 0,
          },
          update: {},
        });
        const remaining = dq.totalQuota - dq.usedQuota;
        if (count > remaining) {
          throw new QuotaExhaustedError(remaining);
        }

        // 2. 写入投票
        const vote = await tx.vote.create({
          data: {
            userId: user.id,
            artistId,
            count,
            source: "QUOTA",
            ip: ip ?? undefined,
            ua: ua ?? undefined,
          },
        });

        // 3. 累加艺人票数
        const artist = await tx.artist.update({
          where: { id: artistId },
          data: { voteCount: { increment: count } },
          select: { id: true, voteCount: true, name: true },
        });

        // 4. 应援关系
        await tx.fanSupport.upsert({
          where: { userId_artistId: { userId: user.id, artistId } },
          create: { userId: user.id, artistId, votedTotal: count },
          update: { votedTotal: { increment: count } },
        });

        // 5. 扣减当日额度
        const updatedDq = await tx.dailyQuota.update({
          where: { userId_date: { userId: user.id, date: today } },
          data: { usedQuota: { increment: count } },
        });

        return {
          vote,
          artist,
          remaining: updatedDq.totalQuota - updatedDq.usedQuota,
        };
      });

      return ok(
        sanitizeBigInt({
          artistId: result.artist.id,
          artistVotes: result.artist.voteCount,
          voteId: result.vote.id,
          remaining: result.remaining,
          dailyQuota,
        }),
      );
    } catch (e) {
      if (e instanceof QuotaExhaustedError) {
        return ERR.QUOTA_EXHAUSTED(
          `今日票数仅剩 ${e.remaining} 票，无法一次投出 ${count} 票`,
        );
      }
      throw e;
    }
  } catch (e) {
    console.error("[POST /api/vote]", e);
    return ERR.INTERNAL();
  }
}

function startOfDay(d = new Date()): Date {
  const x = new Date(d);
  x.setHours(0, 0, 0, 0);
  return x;
}
