import PsychometricStatement, { IPsychometricStatementDocument } from '../models/psychometricStatement';
import PsychometricTest from '../models/psychometricTest';
import PsychometricResult from '../models/psychometricResult';
import { calculateProfile } from '../utils/personalityCalculator';


const err = (msg: string, code = 400): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: code });

// ─── Statement CRUD ──────────────────────────────────────────────────────────

export const createStatement = async (data: {
  statement_text: string;
  dimension: string;
  direction: string;
  sequence?: number;
}) => {
  const existing = await PsychometricStatement.findOne({ statement_text: data.statement_text.trim() });
  if (existing) throw err('Statement already exists');
  return PsychometricStatement.create({ ...data, statement_text: data.statement_text.trim() });
};

export const getAllStatements = async (page = 1, limit = 20, dimension?: string) => {
  const filter = dimension ? { dimension } : {};
  const skip = (page - 1) * limit;
  const [statements, total] = await Promise.all([
    PsychometricStatement.find(filter).sort({ sequence: 1, createdAt: -1 }).skip(skip).limit(limit),
    PsychometricStatement.countDocuments(filter),
  ]);
  return {
    statements,
    pagination: {
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
      hasNextPage: page < Math.ceil(total / limit),
      hasPrevPage: page > 1,
    },
  };
};

export const getStatementById = async (id: string) => {
  const statement = await PsychometricStatement.findById(id);
  if (!statement) throw err('Statement not found', 404);
  return statement;
};

export const updateStatement = async (id: string, data: Partial<{
  statement_text: string;
  dimension: string;
  direction: string;
  sequence: number;
}>) => {
  const statement = await PsychometricStatement.findByIdAndUpdate(id, data, { new: true, runValidators: true });
  if (!statement) throw err('Statement not found', 404);
  return statement;
};

export const deleteStatement = async (id: string) => {
  const statement = await PsychometricStatement.findByIdAndDelete(id);
  if (!statement) throw err('Statement not found', 404);
};

// ─── Test CRUD ───────────────────────────────────────────────────────────────

export const createTest = async (data: {
  title: string;
  description?: string;
  statements: string[];
  status?: string;
}) => {
  if (!data.statements || data.statements.length === 0) throw err('At least one statement is required');

  const found = await PsychometricStatement.find({ _id: { $in: data.statements } }, '_id');
  if (found.length !== data.statements.length) throw err('One or more statement IDs are invalid');

  const existing = await PsychometricTest.findOne({ title: data.title.trim() });
  if (existing) throw err('A test with this title already exists');

  return PsychometricTest.create({ ...data, title: data.title.trim() });
};

export const getAllTests = async (status?: string, page = 1, limit = 10) => {
  const filter = status ? { status } : {};
  const skip = (page - 1) * limit;
  const [tests, total] = await Promise.all([
    PsychometricTest.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit),
    PsychometricTest.countDocuments(filter),
  ]);
  return {
    tests,
    pagination: {
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
      hasNextPage: page < Math.ceil(total / limit),
      hasPrevPage: page > 1,
    },
  };
};

export const getTestById = async (id: string) => {
  const test = await PsychometricTest.findById(id).populate('statements');
  if (!test) throw err('Test not found', 404);
  return test;
};

export const updateTest = async (id: string, data: Partial<{
  title: string;
  description: string;
  statements: string[];
  status: string;
}>) => {
  if (data.statements) {
    const found = await PsychometricStatement.find({ _id: { $in: data.statements } }, '_id');
    if (found.length !== data.statements.length) throw err('One or more statement IDs are invalid');
  }

  if (data.title) {
    const existing = await PsychometricTest.findOne({ title: data.title.trim(), _id: { $ne: id } });
    if (existing) throw err('A test with this title already exists');
    data.title = data.title.trim();
  }

  const test = await PsychometricTest.findByIdAndUpdate(id, data, { new: true, runValidators: true }).populate('statements');
  if (!test) throw err('Test not found', 404);
  return test;
};

export const deleteTest = async (id: string) => {
  const test = await PsychometricTest.findByIdAndDelete(id);
  if (!test) throw err('Test not found', 404);
};

// ─── User: Active Tests ──────────────────────────────────────────────────────

export const getActiveTests = async (userId: string) => {
  const tests = await PsychometricTest.find({ status: 'active' })
    .select('title description')
    .sort({ createdAt: -1 })
    .lean();

  const testIds = tests.map((t) => t._id);
  const results = await PsychometricResult.find(
    { test: { $in: testIds }, user: userId },
    'test personality_type submitted_at',
  ).lean();

  const resultMap = new Map(results.map((r) => [r.test.toString(), r]));

  return tests.map((test) => {
    const result = resultMap.get(test._id.toString());
    return {
      ...test,
      completed: !!result,
      ...(result && { personality_type: result.personality_type, submitted_at: result.submitted_at }),
    };
  });
};

// ─── User: Attempt Test ──────────────────────────────────────────────────────

export const attemptTest = async (testId: string, userId: string) => {
  const test = await PsychometricTest.findById(testId).populate<{ statements: IPsychometricStatementDocument[] }>(
    'statements',
    'statement_text dimension',
  );
  if (!test) throw err('Test not found', 404);
  if (test.status !== 'active') throw err('This test is not active');

  const alreadySubmitted = await PsychometricResult.exists({ test: testId, user: userId });
  if (alreadySubmitted) throw err('You have already completed this test');

  const statements = test.statements
    .sort((a, b) => ((a.sequence ?? 0) - (b.sequence ?? 0)))
    .map((s) => ({ _id: s._id, statement_text: s.statement_text }));

  return {
    test: { _id: test._id, title: test.title, description: test.description },
    statements,
    total: statements.length,
    scale: [
      { value: 1, label: 'Strongly Disagree' },
      { value: 2, label: 'Disagree' },
      { value: 3, label: 'Neutral' },
      { value: 4, label: 'Agree' },
      { value: 5, label: 'Strongly Agree' },
    ],
  };
};

// ─── User: Submit Test ───────────────────────────────────────────────────────

interface ResponseInput {
  statement_id: string;
  value: number;
}

export const submitTest = async (testId: string, userId: string, responses: ResponseInput[]) => {
  const test = await PsychometricTest.findById(testId);
  if (!test) throw err('Test not found', 404);
  if (test.status !== 'active') throw err('This test is not active');

  const alreadySubmitted = await PsychometricResult.exists({ test: testId, user: userId });
  if (alreadySubmitted) throw err('You have already completed this test');

  // Validate all statements are answered
  const statementIds = test.statements.map((s) => s.toString());
  if (responses.length !== statementIds.length) {
    throw err(`All ${statementIds.length} statements must be answered`);
  }

  // Validate response values and statement IDs
  const responseStatementIds = new Set<string>();
  for (const r of responses) {
    if (r.value < 1 || r.value > 5 || !Number.isInteger(r.value)) {
      throw err('Each response value must be an integer between 1 and 5');
    }
    if (!statementIds.includes(r.statement_id)) {
      throw err(`Statement ${r.statement_id} does not belong to this test`);
    }
    if (responseStatementIds.has(r.statement_id)) {
      throw err(`Duplicate response for statement ${r.statement_id}`);
    }
    responseStatementIds.add(r.statement_id);
  }

  // Fetch full statements for calculation
  const statements = await PsychometricStatement.find({ _id: { $in: statementIds } });
  const statementMap = new Map(statements.map((s) => [s._id.toString(), s]));

  const mappedResponses = responses.map((r) => ({ statement: r.statement_id, value: r.value }));
  const { profile, personality_type } = calculateProfile(mappedResponses, statementMap);

  return PsychometricResult.create({
    test: testId,
    user: userId,
    responses: mappedResponses,
    profile,
    personality_type,
  });
};

// ─── User: My Results ────────────────────────────────────────────────────────

export const getMyResults = async (userId: string) => {
  const results = await PsychometricResult.find({ user: userId })
    .populate('test', 'title description')
    .select('test profile personality_type submitted_at')
    .sort({ submitted_at: -1 })
    .lean();

  return results.filter((r) => r.test !== null);
};

// ─── Admin: Get Test Results ─────────────────────────────────────────────────

export const getTestResults = async (testId: string) => {
  const test = await PsychometricTest.findById(testId, 'title');
  if (!test) throw err('Test not found', 404);

  const results = await PsychometricResult.find({ test: testId })
    .populate('user', 'name email department designation')
    .select('user profile personality_type submitted_at')
    .sort({ submitted_at: -1 });

  return { test, results };
};

// ─── Admin: Get Single User Result ───────────────────────────────────────────

export const getUserResult = async (testId: string, userId: string) => {
  const result = await PsychometricResult.findOne({ test: testId, user: userId })
    .populate('user', 'name email department designation')
    .populate('test', 'title description')
    .populate('responses.statement', 'statement_text dimension direction');

  if (!result) throw err('Result not found', 404);
  return result;
};
