import Game from "../models/game";
import GameAttempt from "../models/gameAttempt";
import User from "../models/users";
import { Types } from "mongoose";

// Utility: expected time per difficulty (in seconds)

// Scoring config per difficulty
const sudokuScoring: Record<
  string,
  { base: number; max: number; maxTime: number }
> = {
  easy: { base: 10, max: 40, maxTime: 600 }, // 10 min
  medium: { base: 20, max: 60, maxTime: 1200 }, // 20 min
  hard: { base: 30, max: 90, maxTime: 1800 }, // 30 min
};

export const getSudokuPuzzleService = async (
  difficulty: "easy" | "medium" | "hard",
) => {
  // Get a random puzzle of the requested difficulty
  const count = await Game.countDocuments({ type: "sudoku", difficulty });
  if (count === 0) {
    const err: any = new Error("No puzzles available");
    err.statusCode = 404;
    throw err;
  }
  const rand = Math.floor(Math.random() * count);
  const puzzle = await Game.findOne({ type: "sudoku", difficulty })
    .skip(rand)
    .select("-solution")
    .lean();
  return puzzle;
};

// New: Get a random puzzle from all difficulties
export const getRandomSudokuPuzzleService = async () => {
  const count = await Game.countDocuments({ type: "sudoku", is_active: true });
  if (count === 0) {
    const err: any = new Error("No puzzles available");
    err.statusCode = 404;
    throw err;
  }
  const rand = Math.floor(Math.random() * count);
  const puzzle = await Game.findOne({ type: "sudoku", is_active: true })
    .skip(rand)
    .select("-solution")
    .lean();
  return puzzle;
};

export const startSudokuAttemptService = async (
  userId: Types.ObjectId,
  gameId: Types.ObjectId,
) => {
  // Fetch the puzzle string from the Game document
  const game = await Game.findById(gameId);
  if (!game) throw new Error("Game not found");
  const attempt = await GameAttempt.create({
    user: userId,
    game: gameId,
    answers: game.puzzle, // Initialize with the puzzle string
    status: "in_progress",
    score: 0,
    timeTaken: 0,
    startedAt: new Date(),
  });
  // Format startedAt to India time string
  const { toKolkataTime } = require("../utils/timezone");
  return {
    attemptId: attempt._id,
    startedAt: toKolkataTime(attempt.startedAt, "yyyy-MM-dd hh:mm:ss a"),
  };
};

export const updateSudokuAttemptService = async (
  attemptId: Types.ObjectId,
  answers: string,
) => {
  const attempt = await GameAttempt.findById(attemptId);
  if (!attempt) throw new Error("Attempt not found");
  if (attempt.status !== "in_progress")
    throw new Error("Attempt already completed or failed");
  attempt.answers = answers;
  await attempt.save();
  return attempt;
};

export const submitSudokuAttemptService = async (
  attemptId: Types.ObjectId,
  userId: Types.ObjectId,
  answers: string,
) => {
  const attempt = await GameAttempt.findById(attemptId).populate("game");
  if (!attempt) {
    const err: any = new Error("Attempt not found");
    err.statusCode = 404;
    throw err;
  }
  if (!attempt.game) {
    const err: any = new Error("Game not found");
    err.statusCode = 404;
    throw err;
  }
  if (!attempt.user.equals(userId)) {
    const err: any = new Error("Unauthorized");
    err.statusCode = 403;
    throw err;
  }
  if (attempt.status === "completed") {
    const err: any = new Error("Attempt already completed");
    err.statusCode = 400;
    throw err;
  }
  if (attempt.status !== "in_progress") {
    const err: any = new Error("Attempt already completed or failed");
    err.statusCode = 400;
    throw err;
  }

  // Update attempt.answers before validation
  attempt.answers = answers;
  await attempt.save();

  const now = new Date();
  const timeTaken = Math.floor(
    (now.getTime() - attempt.startedAt.getTime()) / 1000,
  );
  const solution = (attempt.game as any).solution;
  const difficulty = (attempt.game as any).difficulty;

  // Validate answers (must match solution)
  if (attempt.answers !== solution) {
    attempt.status = "failed";
    attempt.completedAt = now;
    attempt.timeTaken = timeTaken;
    attempt.score = 0;
    await attempt.save();
    const { toKolkataTime } = require("../utils/timezone");
    return {
      success: false,
      message: "Incorrect solution",
      score: 0,
      timeTaken,
      startedAt: toKolkataTime(attempt.startedAt, "yyyy-MM-dd hh:mm:ss a"),
      completedAt: toKolkataTime(attempt.completedAt, "yyyy-MM-dd hh:mm:ss a"),
    };
  }

  // --- New Linear Scoring Logic ---
  const config = sudokuScoring[difficulty] || sudokuScoring.medium;
  let score: number;
  if (timeTaken >= config.maxTime) {
    score = config.base;
  } else {
    // Linear interpolation: max score at 0s, base score at maxTime
    score = Math.round(
      config.base +
        (config.max - config.base) *
          ((config.maxTime - timeTaken) / config.maxTime),
    );
    // Clamp between base and max
    score = Math.max(config.base, Math.min(config.max, score));
  }

  attempt.status = "completed";
  attempt.completedAt = now;
  attempt.timeTaken = timeTaken;
  attempt.score = score;
  await attempt.save();

  // Award points to user
  await User.findByIdAndUpdate(userId, {
    $inc: { earned_points: score, sudoku_points: score },
  });

  // TODO: Add activity log entry ("Completed Sudoku (Medium) in 4m 30s")

  const { toKolkataTime } = require("../utils/timezone");
  return {
    success: true,
    score,
    timeTaken,
    startedAt: toKolkataTime(attempt.startedAt, "yyyy-MM-dd hh:mm:ss a"),
    completedAt: toKolkataTime(attempt.completedAt, "yyyy-MM-dd hh:mm:ss a"),
  };
};

export const getMySudokuAttemptsService = async (
  userId: Types.ObjectId,
  limit = 10,
) => {
  const { toKolkataTime } = require("../utils/timezone");
  const attempts = await GameAttempt.find({ user: userId })
    .populate("game", "difficulty")
    .sort({ startedAt: -1 })
    .limit(limit)
    .lean();
  // Format time fields for each attempt
  return attempts.map((a) => ({
    ...a,
    startedAt: a.startedAt
      ? toKolkataTime(a.startedAt, "yyyy-MM-dd hh:mm:ss a")
      : null,
    completedAt: a.completedAt
      ? toKolkataTime(a.completedAt, "yyyy-MM-dd hh:mm:ss a")
      : null,
  }));
};
