import { z } from "zod";
import { protectedProcedure, router } from "../_core/trpc.ts";
import { getDb } from "../db.ts";
import { dailyBonuses, wallets, transactions } from "../../drizzle/schema.ts";
import { eq, desc, sql, and, gte } from "drizzle-orm";

// Streak reward tiers - escalating SC rewards for consecutive logins
const STREAK_REWARDS = [
  { day: 1, gc: 500, sc: 0.10, label: "Day 1" },
  { day: 2, gc: 750, sc: 0.15, label: "Day 2" },
  { day: 3, gc: 1000, sc: 0.20, label: "Day 3" },
  { day: 4, gc: 1250, sc: 0.25, label: "Day 4" },
  { day: 5, gc: 1500, sc: 0.30, label: "Day 5" },
  { day: 6, gc: 2000, sc: 0.40, label: "Day 6" },
  { day: 7, gc: 5000, sc: 1.00, label: "Week Bonus!" },
  { day: 8, gc: 600, sc: 0.12, label: "Day 8" },
  { day: 9, gc: 900, sc: 0.18, label: "Day 9" },
  { day: 10, gc: 1200, sc: 0.24, label: "Day 10" },
  { day: 11, gc: 1500, sc: 0.30, label: "Day 11" },
  { day: 12, gc: 1800, sc: 0.36, label: "Day 12" },
  { day: 13, gc: 2500, sc: 0.50, label: "Day 13" },
  { day: 14, gc: 7500, sc: 2.00, label: "2-Week Bonus!" },
  { day: 15, gc: 800, sc: 0.16, label: "Day 15" },
  { day: 16, gc: 1100, sc: 0.22, label: "Day 16" },
  { day: 17, gc: 1400, sc: 0.28, label: "Day 17" },
  { day: 18, gc: 1700, sc: 0.34, label: "Day 18" },
  { day: 19, gc: 2000, sc: 0.40, label: "Day 19" },
  { day: 20, gc: 2500, sc: 0.50, label: "Day 20" },
  { day: 21, gc: 8000, sc: 2.50, label: "3-Week Bonus!" },
  { day: 22, gc: 1000, sc: 0.20, label: "Day 22" },
  { day: 23, gc: 1300, sc: 0.26, label: "Day 23" },
  { day: 24, gc: 1600, sc: 0.32, label: "Day 24" },
  { day: 25, gc: 1900, sc: 0.38, label: "Day 25" },
  { day: 26, gc: 2200, sc: 0.44, label: "Day 26" },
  { day: 27, gc: 3000, sc: 0.60, label: "Day 27" },
  { day: 28, gc: 4000, sc: 0.80, label: "Day 28" },
  { day: 29, gc: 5000, sc: 1.00, label: "Day 29" },
  { day: 30, gc: 15000, sc: 5.00, label: "Monthly Jackpot!" },
];

function getStreakReward(streakDay: number) {
  const idx = Math.min(streakDay, STREAK_REWARDS.length) - 1;
  return STREAK_REWARDS[Math.max(0, idx)];
}

function isSameDay(d1: Date, d2: Date): boolean {
  return d1.getFullYear() === d2.getFullYear() &&
    d1.getMonth() === d2.getMonth() &&
    d1.getDate() === d2.getDate();
}

function isConsecutiveDay(prev: Date, current: Date): boolean {
  const prevDate = new Date(prev.getFullYear(), prev.getMonth(), prev.getDate());
  const currDate = new Date(current.getFullYear(), current.getMonth(), current.getDate());
  const diffMs = currDate.getTime() - prevDate.getTime();
  const diffDays = diffMs / (1000 * 60 * 60 * 24);
  return diffDays === 1;
}

export const dailyBonusRouter = router({
  /**
   * Get daily bonus status - streak info, next reward, claim availability
   */
  getStatus: protectedProcedure.query(async ({ ctx }) => {
    const db = await getDb();
    if (!db) throw new Error("DB unavailable");

    const now = new Date();

    // Get all claims in the last 31 days to calculate streak
    const thirtyOneDaysAgo = new Date(now.getTime() - 31 * 86400000);
    const recentClaims = await db
      .select()
      .from(dailyBonuses)
      .where(
        and(
          eq(dailyBonuses.userId, ctx.user.id),
          gte(dailyBonuses.claimedAt, thirtyOneDaysAgo)
        )
      )
      .orderBy(desc(dailyBonuses.claimedAt));

    const lastClaim = recentClaims[0] || null;
    const claimedToday = lastClaim ? isSameDay(new Date(lastClaim.claimedAt), now) : false;

    // Calculate current streak
    let currentStreak = 0;
    if (recentClaims.length > 0) {
      // Start from the most recent claim
      let checkDate = new Date(now);
      // If claimed today, start counting from today
      if (claimedToday) {
        currentStreak = 1;
        checkDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        checkDate.setDate(checkDate.getDate() - 1);
      } else if (lastClaim && isConsecutiveDay(new Date(lastClaim.claimedAt), now)) {
        // If last claim was yesterday, streak is still alive
        checkDate = new Date(lastClaim.claimedAt);
        checkDate = new Date(checkDate.getFullYear(), checkDate.getMonth(), checkDate.getDate());
        checkDate.setDate(checkDate.getDate() - 1);
        currentStreak = 1;
      }

      // Count consecutive days backward
      for (let i = claimedToday ? 1 : 0; i < recentClaims.length; i++) {
        const claimDate = new Date(recentClaims[i].claimedAt);
        const claimDay = new Date(claimDate.getFullYear(), claimDate.getMonth(), claimDate.getDate());
        if (isSameDay(claimDay, checkDate)) {
          currentStreak++;
          checkDate.setDate(checkDate.getDate() - 1);
        } else if (claimDay < checkDate) {
          break;
        }
      }
    }

    // If not claimed today and streak is broken (last claim > 1 day ago), reset
    if (!claimedToday && lastClaim) {
      const lastClaimDate = new Date(lastClaim.claimedAt);
      if (!isConsecutiveDay(lastClaimDate, now)) {
        currentStreak = 0;
      }
    }

    const nextStreakDay = claimedToday ? currentStreak + 1 : currentStreak + 1;
    const nextReward = getStreakReward(claimedToday ? currentStreak + 1 : Math.max(1, currentStreak + 1));
    const currentReward = claimedToday ? getStreakReward(currentStreak) : getStreakReward(Math.max(1, currentStreak + 1));

    // Build calendar data for the current 30-day cycle
    const calendar = STREAK_REWARDS.map((reward, idx) => {
      const dayNum = idx + 1;
      const claimed = dayNum <= currentStreak && claimedToday;
      const isToday = dayNum === (claimedToday ? currentStreak : currentStreak + 1);
      const isNext = !claimedToday && dayNum === currentStreak + 1;
      return {
        day: dayNum,
        gc: reward.gc,
        sc: reward.sc,
        label: reward.label,
        claimed: dayNum <= currentStreak,
        isToday,
        isNext,
        isMilestone: [7, 14, 21, 30].includes(dayNum),
      };
    });

    // Get total lifetime daily bonus earnings
    const totalEarnings = await db
      .select({
        totalGc: sql<string>`COALESCE(SUM(${dailyBonuses.gcAmount}), 0)`,
        totalSc: sql<string>`COALESCE(SUM(${dailyBonuses.scAmount}), 0)`,
        totalClaims: sql<number>`COUNT(*)`,
      })
      .from(dailyBonuses)
      .where(eq(dailyBonuses.userId, ctx.user.id));

    return {
      currentStreak,
      claimedToday,
      nextReward: claimedToday ? nextReward : currentReward,
      calendar,
      totalEarnings: {
        gc: parseFloat(totalEarnings[0]?.totalGc || "0"),
        sc: parseFloat(totalEarnings[0]?.totalSc || "0"),
        totalClaims: totalEarnings[0]?.totalClaims || 0,
      },
      longestStreak: currentStreak, // Simplified - in production track separately
    };
  }),

  /**
   * Claim daily bonus
   */
  claim: protectedProcedure.mutation(async ({ ctx }) => {
    const db = await getDb();
    if (!db) throw new Error("DB unavailable");

    const now = new Date();

    // Check if already claimed today
    const lastClaim = await db
      .select()
      .from(dailyBonuses)
      .where(eq(dailyBonuses.userId, ctx.user.id))
      .orderBy(desc(dailyBonuses.claimedAt))
      .limit(1);

    if (lastClaim[0] && isSameDay(new Date(lastClaim[0].claimedAt), now)) {
      throw new Error("Already claimed today! Come back tomorrow.");
    }

    // Calculate streak
    let currentStreak = 0;
    if (lastClaim[0]) {
      const lastDate = new Date(lastClaim[0].claimedAt);
      if (isConsecutiveDay(lastDate, now)) {
        // Get full streak count
        const thirtyOneDaysAgo = new Date(now.getTime() - 31 * 86400000);
        const recentClaims = await db
          .select()
          .from(dailyBonuses)
          .where(
            and(
              eq(dailyBonuses.userId, ctx.user.id),
              gte(dailyBonuses.claimedAt, thirtyOneDaysAgo)
            )
          )
          .orderBy(desc(dailyBonuses.claimedAt));

        currentStreak = 1;
        let checkDate = new Date(lastDate.getFullYear(), lastDate.getMonth(), lastDate.getDate());
        checkDate.setDate(checkDate.getDate() - 1);

        for (let i = 1; i < recentClaims.length; i++) {
          const claimDate = new Date(recentClaims[i].claimedAt);
          const claimDay = new Date(claimDate.getFullYear(), claimDate.getMonth(), claimDate.getDate());
          if (isSameDay(claimDay, checkDate)) {
            currentStreak++;
            checkDate.setDate(checkDate.getDate() - 1);
          } else {
            break;
          }
        }
      }
    }

    const streakDay = currentStreak + 1;
    const reward = getStreakReward(streakDay);

    // Get current wallet
    const walletResult = await db.select().from(wallets).where(eq(wallets.userId, ctx.user.id)).limit(1);
    const wallet = walletResult[0];
    if (!wallet) throw new Error("Wallet not found");

    const gcBefore = parseFloat(wallet.gcBalance);
    const scBefore = parseFloat(wallet.scBalance);

    // Credit GC
    if (reward.gc > 0) {
      await db.update(wallets).set({
        gcBalance: sql`${wallets.gcBalance} + ${reward.gc.toFixed(2)}`,
        lifetimeGcEarned: sql`${wallets.lifetimeGcEarned} + ${reward.gc.toFixed(2)}`,
      }).where(eq(wallets.userId, ctx.user.id));

      await db.insert(transactions).values({
        userId: ctx.user.id,
        type: "daily_bonus" as any,
        currency: "GC" as any,
        amount: reward.gc.toFixed(2) as any,
        balanceBefore: gcBefore.toFixed(2) as any,
        balanceAfter: (gcBefore + reward.gc).toFixed(2) as any,
        referenceType: "daily_streak",
        referenceId: `streak-day-${streakDay}`,
        description: `Daily bonus streak Day ${streakDay}: ${reward.label}`,
      });
    }

    // Credit SC
    if (reward.sc > 0) {
      await db.update(wallets).set({
        scBalance: sql`${wallets.scBalance} + ${reward.sc.toFixed(2)}`,
        lifetimeScEarned: sql`${wallets.lifetimeScEarned} + ${reward.sc.toFixed(2)}`,
      }).where(eq(wallets.userId, ctx.user.id));

      await db.insert(transactions).values({
        userId: ctx.user.id,
        type: "daily_bonus" as any,
        currency: "SC" as any,
        amount: reward.sc.toFixed(2) as any,
        balanceBefore: scBefore.toFixed(2) as any,
        balanceAfter: (scBefore + reward.sc).toFixed(2) as any,
        referenceType: "daily_streak",
        referenceId: `streak-day-${streakDay}`,
        description: `Daily bonus streak Day ${streakDay}: ${reward.label}`,
      });
    }

    // Record the claim
    await db.insert(dailyBonuses).values({
      userId: ctx.user.id,
      spinResult: streakDay,
      gcAmount: reward.gc.toFixed(2),
      scAmount: reward.sc.toFixed(2),
    });

    return {
      streakDay,
      reward: {
        gc: reward.gc,
        sc: reward.sc,
        label: reward.label,
      },
      newStreak: streakDay,
      isMilestone: [7, 14, 21, 30].includes(streakDay),
    };
  }),

  /**
   * Get claim history
   */
  getHistory: protectedProcedure
    .input(z.object({ limit: z.number().min(1).max(100).default(30) }))
    .query(async ({ ctx, input }) => {
      const db = await getDb();
      if (!db) return [];

      const claims = await db
        .select()
        .from(dailyBonuses)
        .where(eq(dailyBonuses.userId, ctx.user.id))
        .orderBy(desc(dailyBonuses.claimedAt))
        .limit(input.limit);

      return claims.map((c) => ({
        id: c.id,
        gcAmount: parseFloat(c.gcAmount),
        scAmount: parseFloat(c.scAmount),
        streakDay: c.spinResult,
        claimedAt: c.claimedAt,
      }));
    }),

  /**
   * Get streak rewards table
   */
  getRewardsTable: protectedProcedure.query(async () => {
    return STREAK_REWARDS;
  }),
});
