import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import {
  getOrganizationIdOrGlobal,
  getCampusId,
  assertAuthenticatedTenantUser,
  hasRoleAccess,
} from '@/services/shared/access';
import { PERSONALITY_REPORT_ROLE_NAMES } from '@/shared/constants/personality';
import type { PersonalityQuizResults } from '@/db/models/personality_quiz_results';
import type { CurrentUser } from '@/db/api/types';

interface PersonalityInput {
  personality_type: string;
  quiz_answers: unknown;
}

interface PersonalityFilter {
  campusId?: string;
}

function assertValidResult(data: PersonalityInput): void {
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    throw new ValidationError();
  }

  if (
    typeof data.personality_type !== 'string' ||
    data.personality_type.trim().length === 0 ||
    !data.quiz_answers ||
    typeof data.quiz_answers !== 'object' ||
    Array.isArray(data.quiz_answers)
  ) {
    throw new ValidationError();
  }

  const answerValues = Object.values(data.quiz_answers);
  if (
    !answerValues.every(
      (value) => typeof value === 'string' && value.trim().length > 0,
    )
  ) {
    throw new ValidationError();
  }
}

function toDto(record: PersonalityQuizResults | null) {
  if (!record) {
    return null;
  }

  const plain = record.get({ plain: true });

  return {
    id: plain.id,
    personality_type: plain.personality_type,
    quiz_answers: plain.quiz_answers,
    completed_at: plain.completed_at,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    userId: plain.userId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class PersonalityQuizResultsService {
  static async getCurrentUserResult(currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};

    const record = await db.personality_quiz_results.findOne({
      where: {
        ...orgFilter,
        userId: currentUser?.id ?? null,
      },
      order: [['updatedAt', 'desc']],
    });

    return toDto(record);
  }

  static async upsertCurrentUserResult(
    data: PersonalityInput,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);
    assertValidResult(data);

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};
    const where = {
      ...orgFilter,
      userId: currentUser?.id ?? null,
    };
    return withTransaction(async (transaction) => {
      const existing = await db.personality_quiz_results.findOne({
        where,
        transaction,
      });

      const payload = {
        personality_type: data.personality_type.trim().toUpperCase(),
        quiz_answers: data.quiz_answers,
        completed_at: new Date(),
        organizationId,
        campusId: getCampusId(currentUser),
        userId: currentUser?.id ?? null,
        updatedById: currentUser?.id ?? null,
      };

      let saved: PersonalityQuizResults;
      if (existing) {
        saved = await existing.update(payload, { transaction });
      } else {
        saved = await db.personality_quiz_results.create(
          {
            ...payload,
            createdById: currentUser?.id ?? null,
          },
          { transaction },
        );
      }

      return toDto(saved);
    });
  }

  static async distribution(
    filter: PersonalityFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);

    if (!hasRoleAccess(currentUser, PERSONALITY_REPORT_ROLE_NAMES)) {
      throw new ForbiddenError();
    }

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};

    const rows = await db.personality_quiz_results.findAll({
      attributes: [
        'personality_type',
        [db.sequelize.fn('COUNT', db.sequelize.col('id')), 'count'],
      ],
      where: {
        ...orgFilter,
        ...(filter.campusId ? { campusId: filter.campusId } : {}),
      },
      group: ['personality_type'],
      order: [[db.sequelize.fn('COUNT', db.sequelize.col('id')), 'desc']],
    });

    return {
      rows: rows.map((row) => ({
        type: row.get('personality_type'),
        count: Number(row.get('count')),
      })),
      count: rows.length,
    };
  }
}

export default PersonalityQuizResultsService;
