/* eslint-disable @typescript-eslint/no-unused-vars */
import type * as Party from 'partykit/server';
import { createClerkClient, verifyToken } from '@clerk/backend';

export interface Upgrade {
  id: string;
  name: string;
  description: string;
  baseCost: number;
  multiplier: number;
  clickBonus?: number;
  autoClickRate?: number;
  clickMultiplierBonus?: number;
  icon: string;
  mascotTiers?: MascotTier[];
  oneTime?: boolean;
  newsTitles?: string[];
  youtubeId?: string;
}

export interface MascotTier {
  level: number;
  imageSrc: string;
  multiplier: number;
  rarity: number;
}


interface UserState {
  name: string;
  clicks: number;
  lastSeen: number;
  bonusMultiplier: number;
  lastClickTime?: number;
  upgrades: Record<string, { owned: number; cost: number }>;
  clickMultiplier: number;
  autoClickRate: number;
  ratPoisonChallenge?: {
    challengeString: string;
    expiresAt: number;
  };
  ratPoisonImmunityUntil?: number;
}

interface GameState {
  totalClicks: number;
  users: Record<string, UserState>;
  milestones: Record<string, boolean>;
  currentBackground: string;
  currentClickImage: string;
}

interface AuthenticatedMessage {
  token: string;
  userId: string; // Expected userId from client
  userName: string; // Expected userName from client
}

interface ClickMessage extends AuthenticatedMessage {
  type: 'click';
}

interface PurchaseUpgradeMessage extends AuthenticatedMessage {
  type: 'purchase-upgrade';
  upgradeId: string;
}

interface ApplyMultiplierBonusMessage extends AuthenticatedMessage { // New message type
  type: 'apply-multiplier-bonus';
  multiplierBonus: number;
}

interface AdminBroadcastMessage extends AuthenticatedMessage {
  type: 'admin-broadcast';
  message: string;
  targetUserId?: string; // Optional: if broadcasting to a specific user
}

interface UserJoinMessage extends AuthenticatedMessage {
  type: 'user-join';
}

interface EditUserMessage extends AuthenticatedMessage {
  type: 'edit-user';
  targetUserId: string;
  clicks: number;
  upgrades: Record<string, { owned: number; cost: number }>;
}

interface ThrowRatPoisonMessage extends AuthenticatedMessage {
  type: 'throw-rat-poison';
  targetUserId: string;
}

interface SolveRatPoisonMessage extends AuthenticatedMessage {
  type: 'solve-rat-poison';
  challengeString: string;
}

type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage | AdminBroadcastMessage | EditUserMessage | ThrowRatPoisonMessage | SolveRatPoisonMessage; // Updated Message type

import { UPGRADES as ALL_UPGRADES } from '../src/config/upgrades';

const UPGRADES = ALL_UPGRADES.filter(upgrade => !upgrade.oneTime);
const ONE_TIME_UPGRADES = ALL_UPGRADES.filter(upgrade => upgrade.oneTime);

const MILESTONES = [
  { threshold: 1000, id: 'first-thousand', background: 'rainbow', image: 'https://media1.tenor.com/m/x8v1oNUOmg4AAAAd/spinning-rat-rat.gif' },
  { threshold: 10000, id: 'ten-thousand', background: 'cyberpunk', image: 'https://cdn.discordapp.com/emojis/1297946545626288312.webp' },
  { threshold: 50000, id: 'epic-milestone', background: 'space', image: 'https://media.discordapp.net/stickers/1377379131649429554.gif?size=160&quality=lossless' },
  { threshold: 100000, id: 'legendary', background: 'musume', image: 'https://media1.tenor.com/m/pV74fmh_NLgAAAAd/louie-rat-spinning-rat.gif' },
  { threshold: 200000, id: 'two-hundred-thousand', background: 'glitch', image: 'https://media1.tenor.com/m/pV74fmh_NLgAAAAd/louie-rat-spinning-rat.gif' },
  { threshold: 696969, id: 'ultimate', background: 'ultimate', image: 'https://media.discordapp.net/stickers/1397981135266648064.webp?size=160&quality=lossless' },
  { threshold: 1000000, id: 'god-tier', background: 'god-tier', image: 'https://media.discordapp.net/stickers/1294812453112123453.webp?quality=lossless' },
  { threshold: 5000000, id: 'mega-bozo', background: 'mega-bozo', image: 'https://media1.tenor.com/m/x8v1oNUOmg4AAAAd/spinning-rat-rat.gif' },
  { threshold: 8008008, id: 'galactic-bozo', background: 'galactic-bozo', image: 'https://cdn.discordapp.com/emojis/1297946545626288312.webp' },
  { threshold: 10000000, id: 'cosmic-bozo', background: 'cosmic-bozo', image: 'https://media.discordapp.net/stickers/1377379131649429554.gif?size=160&quality=lossless' },
  { threshold: 100000000, id: 'interdimensional-bozo', background: 'interdimensional', image: 'https://tenor.com/view/horizontally-spinning-rat-gif-2222021229431992839.gif' },
  { threshold: 500000000, id: 'multiversal-bozo', background: 'multiversal', image: 'https://tenor.com/view/spinning-rat-gif-26132.gif' },
  { threshold: 1000000000, id: 'billionaire-bozo', background: 'billionaire', image: 'https://tenor.com/view/dancing-rat-rat-dance-gif-25151223.gif' },
  { threshold: 10000000000, id: 'ten-billion-bozo', background: 'ten-billion', image: 'https://tenor.com/view/horizontally-spinning-rat-gif-2222021229431992839.gif' },
  { threshold: 100000000000, id: 'hundred-billion-bozo', background: 'hundred-billion', image: 'https://tenor.com/view/spinning-rat-gif-26132.gif' },
  { threshold: 500000000000, id: 'half-trillion-bozo', background: 'half-trillion', image: 'https://tenor.com/view/dancing-rat-rat-dance-gif-25151223.gif' },
  { threshold: 1000000000000, id: 'trillionaire-bozo', background: 'trillionaire', image: 'https://tenor.com/view/dancing-rat-rat-dance-gif-25151223.gif' }
];

export default class GameServer implements Party.Server {
  clerkClient: ReturnType<typeof createClerkClient>;
  private userConnections: Map<string, Party.Connection> = new Map(); // Map userId to connection

  constructor(readonly party: Party.Party) {
    this.clerkClient = createClerkClient({
      secretKey: party.env.CLERK_SECRET_KEY as string,
    });
  }

  gameState: GameState = {
    totalClicks: 0,
    users: {},
    milestones: {},
    currentBackground: 'default',
    currentClickImage: 'https://media1.tenor.com/m/pV74fmh_NLgAAAAd/louie-rat-spinning-rat.gif'
  };

  autoClickInterval?: NodeJS.Timeout;

  async onConnect(conn: Party.Connection, _ctx: Party.ConnectionContext) {
    conn.send(JSON.stringify({ type: 'game-state', state: this.gameState }));
  }

  onClose(conn: Party.Connection) {
    // Remove the connection from the map when it closes
    for (const [userId, connection] of this.userConnections.entries()) {
      if (connection === conn) {
        this.userConnections.delete(userId);
        break;
      }
    }
  }

  async onMessage(message: string, sender: Party.Connection) {
    const data = JSON.parse(message) as Message;

    let currentUserId: string;
    let currentUserName: string;
    let isAuthenticated = false;

    // Verify the Clerk token for authenticated messages
    if ('token' in data && data.token) {
      try {
        const sessionClaims = await verifyToken(data.token, {
          jwtKey: this.party.env.CLERK_JWT_KEY as string,
        });
        currentUserId = sessionClaims.sub;
        isAuthenticated = true;

        // Ensure the userId from the client matches the authenticated userId
        if (currentUserId !== data.userId) {
          console.warn(`User ID mismatch: Client sent ${data.userId}, but token is for ${currentUserId}`);
          sender.close(); // Close connection for potential tampering
          return;
        }
        currentUserName = data.userName; // Use name from client, which comes from Clerk
      } catch (error) {
        console.error('Clerk token verification failed:', error);
        sender.close(); // Close connection if token is invalid
        return;
      }
    } else {
      // Handle unauthenticated messages (guest users)
      currentUserId = data.userId;
      currentUserName = data.userName;
      isAuthenticated = false;

      // Only allow 'user-join' for unauthenticated users.
      // Other actions like 'click', 'purchase-upgrade', 'apply-multiplier-bonus' are ignored.
      if (data.type !== 'user-join') {
        console.warn(`Received unauthenticated message of type '${data.type}' from ${currentUserId}. Action ignored.`);
        this.broadcast(); // Still broadcast game state to keep guests updated
        return;
      }
    }

    // Only update user info in gameState for authenticated users
    if (isAuthenticated) {
      if (!this.gameState.users[currentUserId]) {
        this.gameState.users[currentUserId] = {
          name: currentUserName,
          clicks: 0,
          lastSeen: Date.now(),
          bonusMultiplier: 1, // Initialize bonus multiplier
          upgrades: ALL_UPGRADES.reduce((acc, upgrade) => {
            acc[upgrade.id] = { owned: 0, cost: upgrade.baseCost };
            return acc;
          }, {} as Record<string, { owned: number; cost: number }>),
          clickMultiplier: 1,
          autoClickRate: 0,
          ratPoisonChallenge: undefined,
          ratPoisonImmunityUntil: undefined,
        };
      }
      this.gameState.users[currentUserId].lastSeen = Date.now();
      this.gameState.users[currentUserId].name = currentUserName; // Update name in case it changed
      // Ensure bonusMultiplier is initialized if it somehow wasn't
      if (this.gameState.users[currentUserId].bonusMultiplier === undefined) {
        this.gameState.users[currentUserId].bonusMultiplier = 1;
      }
      // Store the connection for targeted broadcasts
      this.userConnections.set(currentUserId, sender);
    }

    switch (data.type) {
      case 'user-join':
        // For authenticated users, user join is handled by the user state update above.
        // For guest users, no action is needed here as they are not added to gameState.users.
        break;
      case 'click':
        if (isAuthenticated) {
          this.handleClick(data, currentUserId);
        } else {
          console.warn(`Unauthenticated click from ${currentUserId} ignored.`);
        }
        break;
      case 'purchase-upgrade':
        if (isAuthenticated) {
          this.handlePurchaseUpgrade(data, currentUserId);
        } else {
          console.warn(`Unauthenticated purchase-upgrade from ${currentUserId} ignored.`);
        }
        break;
      case 'apply-multiplier-bonus':
        if (isAuthenticated) {
          this.handleApplyMultiplierBonus(data, currentUserId);
        } else {
          console.warn(`Unauthenticated apply-multiplier-bonus from ${currentUserId} ignored.`);
        }
        break;
      case 'admin-broadcast':
        if (isAuthenticated && currentUserId === this.party.env.CLERK_ADMIN_USERID) {
          this.handleAdminBroadcast(data);
        } else {
          console.warn(`Unauthorized admin broadcast attempt from ${currentUserId}.`);
        }
        break;
      case 'edit-user':
        if (isAuthenticated && currentUserId === this.party.env.CLERK_ADMIN_USERID) {
          this.handleEditUser(data);
        } else {
          console.warn(`Unauthorized edit user attempt from ${currentUserId}.`);
        }
        break;
      case 'throw-rat-poison':
        if (isAuthenticated) {
          this.handleThrowRatPoison(data, currentUserId);
        } else {
          console.warn(`Unauthenticated throw-rat-poison from ${currentUserId} ignored.`);
        }
        break;
      case 'solve-rat-poison':
        if (isAuthenticated) {
          this.handleSolveRatPoison(data, currentUserId);
        } else {
          console.warn(`Unauthenticated solve-rat-poison from ${currentUserId} ignored.`);
        }
        break;
    }

    this.broadcast();
  }

  handleEditUser(data: EditUserMessage) {
    const { targetUserId, clicks, upgrades } = data;
    const userState = this.gameState.users[targetUserId];

    if (userState) {
      userState.clicks = clicks;
      userState.upgrades = upgrades;
      this.updateGameMultipliers(targetUserId);
    }
  }

  handleThrowRatPoison(data: ThrowRatPoisonMessage, authenticatedUserId: string) {
    const { targetUserId } = data;
    const targetUserState = this.gameState.users[targetUserId];
    const attackerUserState = this.gameState.users[authenticatedUserId];

    if (!targetUserState || !attackerUserState || targetUserId === authenticatedUserId) {
      return; // Cannot poison self or non-existent user
    }

    const now = Date.now();
    if (targetUserState.ratPoisonImmunityUntil && targetUserState.ratPoisonImmunityUntil > now) {
      // Target is immune, notify attacker
      const attackerConn = this.userConnections.get(authenticatedUserId);
      if (attackerConn) {
        attackerConn.send(JSON.stringify({
          type: 'rat-poison-feedback',
          message: `${targetUserState.name} is currently immune to rat poison!`
        }));
      }
      return;
    }

    // Get news headlines for challenge string
    const newsUpgrade = ALL_UPGRADES.find(u => u.id === 'news');
    const newsTitles = newsUpgrade?.newsTitles || [];
    if (newsTitles.length === 0) {
      console.warn('No news titles found for rat poison challenge.');
      return;
    }

    const challengeString = newsTitles[Math.floor(Math.random() * newsTitles.length)];
    const challengeDuration = 10 * 1000; // 10 seconds to solve
    const expiresAt = now + challengeDuration;

    targetUserState.ratPoisonChallenge = {
      challengeString,
      expiresAt,
    };

    // Notify target user to start challenge
    const targetConn = this.userConnections.get(targetUserId);
    if (targetConn) {
      targetConn.send(JSON.stringify({
        type: 'rat-poison-challenge',
        challenge: challengeString,
        expiresAt,
      }));
    }

    // Set a timeout for the challenge to expire if not solved
    setTimeout(() => {
      if (targetUserState.ratPoisonChallenge && targetUserState.ratPoisonChallenge.expiresAt === expiresAt) {
        // Challenge not solved in time
        this.applyRatPoisonPenalty(targetUserId);
        this.broadcast();
      }
    }, challengeDuration + 500); // Give a small buffer

    this.broadcast();
  }

  handleSolveRatPoison(data: SolveRatPoisonMessage, authenticatedUserId: string) {
    const userState = this.gameState.users[authenticatedUserId];
    if (!userState || !userState.ratPoisonChallenge) {
      return; // No active challenge
    }

    const now = Date.now();
    if (now > userState.ratPoisonChallenge.expiresAt) {
      // Challenge expired
      this.applyRatPoisonPenalty(authenticatedUserId);
    } else if (data.challengeString === userState.ratPoisonChallenge.challengeString) {
      // Challenge solved successfully
      userState.ratPoisonImmunityUntil = now + (2 * 60 * 1000); // 2 minutes immunity
      const userConn = this.userConnections.get(authenticatedUserId);
      if (userConn) {
        userConn.send(JSON.stringify({
          type: 'rat-poison-result',
          success: true,
          message: 'Challenge solved! You are immune for 2 minutes.'
        }));
      }
    } else {
      // Challenge failed (wrong string)
      this.applyRatPoisonPenalty(authenticatedUserId);
      const userConn = this.userConnections.get(authenticatedUserId);
      if (userConn) {
        userConn.send(JSON.stringify({
          type: 'rat-poison-result',
          success: false,
          message: 'Incorrect string! You lost clicks and are immune for 20 seconds.'
        }));
      }
    }

    userState.ratPoisonChallenge = undefined; // Clear the challenge
    this.broadcast();
  }

  applyRatPoisonPenalty(userId: string) {
    const userState = this.gameState.users[userId];
    if (!userState) return;

    const clicksLost = Math.floor(userState.clicks * 0.30);
    userState.clicks -= clicksLost;
    userState.ratPoisonImmunityUntil = Date.now() + (20 * 1000); // 20 seconds immunity

    const userConn = this.userConnections.get(userId);
    if (userConn) {
      userConn.send(JSON.stringify({
        type: 'rat-poison-result',
        success: false,
        message: `You failed the challenge and lost ${clicksLost} clicks! You are immune for 20 seconds.`
      }));
    }
  }

  // Define a minimum interval between clicks (e.g., 100ms for 10 clicks/second)
  // This helps prevent simple auto-clicker scripts by rate-limiting server-side.
  private readonly MIN_CLICK_INTERVAL = 50; // milliseconds

  // handleUserJoin is now fully integrated into onMessage and can be removed or simplified.
  // Removing it as its logic is now directly in onMessage.
  // handleUserJoin(data: UserJoinMessage) {
  //   // This function is now mostly handled by the onMessage token verification logic
  //   // It ensures the user exists in gameState.users and updates lastSeen/name.
  //   // No explicit action needed here beyond what onMessage does.
  // }

  handleClick(data: ClickMessage, authenticatedUserId: string) {
    const now = Date.now();
    const userState = this.gameState.users[authenticatedUserId];

    // Ensure user exists (should be handled by onMessage's token verification)
    if (!userState) {
      console.warn(`User ${authenticatedUserId} not found for click handling.`);
      return;
    }

    // Implement rate limiting
    if (userState.lastClickTime && (now - userState.lastClickTime < this.MIN_CLICK_INTERVAL)) {
      console.warn(`Rate limit exceeded for user ${authenticatedUserId}. Click ignored.`);
      return;
    }

    // Apply global click multiplier and user-specific bonus multiplier
    const userBonusMultiplier = userState.bonusMultiplier || 1;
    const clickValue = userState.clickMultiplier * userBonusMultiplier;

    this.gameState.totalClicks += clickValue;
    userState.clicks += clickValue;
    userState.lastSeen = now;
    userState.lastClickTime = now; // Update last valid click time

    this.checkMilestones();
  }

  handleAdminBroadcast(data: AdminBroadcastMessage) {
    const broadcastPayload = {
      type: 'admin-message',
      message: data.message,
      sender: 'Admin'
    };

    if (data.targetUserId) {
      // Send to a specific user using the stored connection
      const targetConn = this.userConnections.get(data.targetUserId);
      if (targetConn) {
        targetConn.send(JSON.stringify(broadcastPayload));
        console.log(`Admin broadcasted to user ${data.targetUserId}: ${data.message}`);
      } else {
        console.warn(`Target user ${data.targetUserId} not found or not connected.`);
      }
    } else {
      // Broadcast to all connections
      this.party.broadcast(JSON.stringify(broadcastPayload));
      console.log(`Admin broadcasted to all users: ${data.message}`);
    }
  }

  handleApplyMultiplierBonus(data: ApplyMultiplierBonusMessage, authenticatedUserId: string) {
    if (!this.gameState.users[authenticatedUserId]) {
      console.warn(`User ${authenticatedUserId} not found for multiplier bonus application.`);
      return;
    }
    // Apply the compounding multiplier bonus
    this.gameState.users[authenticatedUserId].bonusMultiplier *= data.multiplierBonus;
    console.log(`User ${authenticatedUserId} bonus multiplier updated to: ${this.gameState.users[authenticatedUserId].bonusMultiplier}`);
  }

  handlePurchaseUpgrade(data: PurchaseUpgradeMessage, authenticatedUserId: string) {
    const upgradeConfig = UPGRADES.find(u => u.id === data.upgradeId);
    const userState = this.gameState.users[authenticatedUserId];

    if (!upgradeConfig || !userState) return;

    const currentUpgradeState = userState.upgrades[data.upgradeId];

    // Prevent purchasing one-time upgrades if already owned
    if (upgradeConfig.oneTime && currentUpgradeState.owned > 0) {
      console.warn(`Attempted to re-purchase one-time upgrade: ${data.upgradeId}`);
      return;
    }

    // Check affordability against user's clicks
    if (userState.clicks >= currentUpgradeState.cost) {
      userState.clicks -= currentUpgradeState.cost; // Deduct from user's clicks
      currentUpgradeState.owned += 1;

      // For one-time upgrades, cost doesn't change after first purchase
      if (!upgradeConfig.oneTime) {
        currentUpgradeState.cost = Math.floor(upgradeConfig.baseCost * Math.pow(upgradeConfig.multiplier, currentUpgradeState.owned));
      }

      this.updateGameMultipliers(authenticatedUserId);
    }
  }

  updateGameMultipliers(userId: string, autoClickRateOverride?: number) {
    const userState = this.gameState.users[userId];
    if (!userState) return;

    userState.clickMultiplier = 1;
    userState.autoClickRate = 0;

    Object.entries(userState.upgrades).forEach(([upgradeId, upgradeState]) => {
      const config = UPGRADES.find(u => u.id === upgradeId);
      if (!config) return;

      if (config.clickBonus) {
        userState.clickMultiplier += config.clickBonus * upgradeState.owned;
      }
      if (config.autoClickRate) {
        userState.autoClickRate += config.autoClickRate * upgradeState.owned;
      }
    });

    if (autoClickRateOverride !== undefined) {
      userState.autoClickRate = autoClickRateOverride;
    }

    this.setupAutoClicker();
  }

  setupAutoClicker() {
    if (this.autoClickInterval) {
      clearInterval(this.autoClickInterval);
    }

    this.autoClickInterval = setInterval(() => {
      let totalAutoClicks = 0;
      Object.values(this.gameState.users).forEach(user => {
        if (user.autoClickRate > 0) {
          user.clicks += user.autoClickRate;
          totalAutoClicks += user.autoClickRate;
        }
      });

      if (totalAutoClicks > 0) {
        this.gameState.totalClicks += totalAutoClicks;
        this.checkMilestones();
        this.broadcast();
      }
    }, 1000);
  }

  checkMilestones() {
    MILESTONES.forEach(milestone => {
      if (this.gameState.totalClicks >= milestone.threshold && !this.gameState.milestones[milestone.id]) {
        this.gameState.milestones[milestone.id] = true;
        this.gameState.currentBackground = milestone.background;
        this.gameState.currentClickImage = milestone.image;
      }
    });
  }

  broadcast() {
    this.party.broadcast(JSON.stringify({ type: 'game-state', state: this.gameState }));
  }
}

GameServer satisfies Party.Worker;
