import { Op } from 'sequelize';
import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import { resolvePagination } from '@/shared/constants/pagination';
import ValidationError from '@/shared/errors/validation';
import ForbiddenError from '@/shared/errors/forbidden';
import {
  getOrganizationIdOrGlobal,
  getCampusId,
  getSchoolId,
  getClassId,
  assertAuthenticatedTenantUser,
  campusDimensionScope,
  hasFeaturePermission,
  getDisplayName,
  isActingInOwnScope,
  requireUserId,
  getRoleScope,
} from '@/services/shared/access';
import { ROLE_NAMES, ROLE_SCOPES } from '@/shared/constants/roles';
import { FEATURE_PERMISSIONS } from '@/shared/constants/product-permissions';
import type { SafetyQuizResults } from '@/db/models/safety_quiz_results';
import type { Users } from '@/db/models/users';
import type { CurrentUser } from '@/db/api/types';
import { toWeekStartIso } from '@/shared/constants/week';

interface SafetyQuizInput {
  quiz_id: string;
  quiz_title: string;
  week_of: string;
  score: number;
  total_questions: number;
  answers: unknown;
}

interface SafetyQuizFilter {
  week_of?: string;
  limit?: number | string;
  page?: number | string;
}

const REQUIRED_STRINGS = ['quiz_id', 'quiz_title', 'week_of'] as const;
const REPORT_STAFF_ROLES = Object.freeze([
  ROLE_NAMES.OWNER,
  ROLE_NAMES.SUPERINTENDENT,
  ROLE_NAMES.PRINCIPAL,
  ROLE_NAMES.REGISTRAR,
  ROLE_NAMES.DIRECTOR,
  ROLE_NAMES.OFFICE_MANAGER,
  ROLE_NAMES.TEACHER,
  ROLE_NAMES.SUPPORT_STAFF,
]);

interface StaffTenantInfo {
  readonly tenantName: string | null;
  readonly tenantLogo: string | null;
}

function getProductRole(currentUser?: CurrentUser): string {
  return currentUser?.app_role?.name ?? ROLE_NAMES.TEACHER;
}

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

  const hasMissingString = REQUIRED_STRINGS.some((field) => {
    const value = data[field];
    return typeof value !== 'string' || value.trim().length === 0;
  });

  if (
    hasMissingString ||
    !Number.isInteger(data.score) ||
    !Number.isInteger(data.total_questions) ||
    !Array.isArray(data.answers) ||
    !data.answers.every((value) => Number.isInteger(value))
  ) {
    throw new ValidationError();
  }
}

function requireWeekStart(value: string): string {
  const weekStart = toWeekStartIso(value);
  if (!weekStart) {
    throw new ValidationError();
  }
  return weekStart;
}

function optionalWeekStart(value?: string): string | undefined {
  return value ? requireWeekStart(value) : undefined;
}

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

  return {
    id: plain.id,
    quiz_id: plain.quiz_id,
    quiz_title: plain.quiz_title,
    week_of: plain.week_of,
    score: plain.score,
    total_questions: plain.total_questions,
    answers: plain.answers,
    user_name: plain.user_name,
    user_role: plain.user_role,
    completed_at: plain.completed_at,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    userId: plain.userId,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

function assertCanReadCompletion(currentUser?: CurrentUser): void {
  assertAuthenticatedTenantUser(currentUser);
  if (
    hasFeaturePermission(
      currentUser,
      FEATURE_PERMISSIONS.READ_SAFETY_QUIZ_REPORTS,
    )
  ) {
    return;
  }
  throw new ForbiddenError();
}

async function staffScopeWhere(currentUser?: CurrentUser) {
  const organizationId = getOrganizationIdOrGlobal(currentUser);
  const base = organizationId ? { organizationId } : {};
  const scope = getRoleScope(currentUser);

  if (scope === ROLE_SCOPES.SCHOOL) {
    const schoolId = getSchoolId(currentUser);
    if (!schoolId) {
      return base;
    }
    const campuses = await db.campuses.findAll({
      attributes: ['id'],
      where: {
        schoolId,
        ...(organizationId ? { organizationId } : {}),
      },
    });
    const campusIds = campuses.map((campus) => campus.id);
    const classes = campusIds.length > 0
      ? await db.classes.findAll({
          attributes: ['id'],
          where: {
            campusId: campusIds,
            ...(organizationId ? { organizationId } : {}),
          },
        })
      : [];
    const classIds = classes.map((classroom) => classroom.id);

    return {
      ...base,
      [Op.or]: [
        { schoolId },
        ...(campusIds.length > 0 ? [{ campusId: { [Op.in]: campusIds } }] : []),
        ...(classIds.length > 0 ? [{ classId: { [Op.in]: classIds } }] : []),
      ],
    };
  }

  if (scope === ROLE_SCOPES.CAMPUS) {
    const campusId = getCampusId(currentUser);
    return campusId ? { ...base, campusId } : base;
  }

  if (scope === ROLE_SCOPES.CLASS) {
    const classId = getClassId(currentUser);
    return classId ? { ...base, classId } : base;
  }

  return base;
}

function displayNameOf(user: Users): string {
  return [user.firstName, user.lastName].filter(Boolean).join(' ').trim()
    || user.email
    || 'Staff Member';
}

function avatarOf(user: Users): string | null {
  return user.avatar?.[0]?.privateUrl ?? null;
}

function tenantOf(user: Users): StaffTenantInfo {
  const tenant = user.class ?? user.campus ?? user.school ?? user.organizations ?? null;
  return {
    tenantName: tenant?.name ?? null,
    tenantLogo: tenant?.logo ?? null,
  };
}

function latestResultByUser(
  results: readonly SafetyQuizResults[],
): ReadonlyMap<string, SafetyQuizResults> {
  const byUser = new Map<string, SafetyQuizResults>();
  for (const result of results) {
    const userId = result.userId;
    if (!userId || byUser.has(userId)) {
      continue;
    }
    byUser.set(userId, result);
  }
  return byUser;
}

class SafetyQuizResultsService {
  static async list(filter: SafetyQuizFilter, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);
    const weekOf = optionalWeekStart(filter.week_of);

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

    const result = await db.safety_quiz_results.findAndCountAll({
      where: {
        ...orgFilter,
        ...(hasFeaturePermission(
          currentUser,
          FEATURE_PERMISSIONS.READ_SAFETY_QUIZ_REPORTS,
        )
          ? campusDimensionScope(currentUser)
          : { userId: currentUser?.id ?? null }),
        ...(weekOf ? { week_of: weekOf } : {}),
      },
      order: [['completed_at', 'desc']],
      limit,
      offset,
    });

    return {
      rows: result.rows.map(toDto),
      count: result.count,
    };
  }

  static async create(data: SafetyQuizInput, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    assertValidResult(data);

    if (!isActingInOwnScope(currentUser)) {
      return null;
    }

    const organizationId = getOrganizationIdOrGlobal(currentUser);

    return withTransaction(async (transaction) => {
      const weekOf = requireWeekStart(data.week_of);
      const created = await db.safety_quiz_results.create(
        {
          quiz_id: data.quiz_id.trim(),
          quiz_title: data.quiz_title.trim(),
          week_of: weekOf,
          score: data.score,
          total_questions: data.total_questions,
          answers: data.answers,
          user_name: getDisplayName(currentUser),
          user_role: getProductRole(currentUser),
          completed_at: new Date(),
          organizationId,
          campusId: getCampusId(currentUser),
          userId: currentUser?.id ?? null,
          createdById: currentUser?.id ?? null,
          updatedById: currentUser?.id ?? null,
        },
        { transaction },
      );

      return toDto(created);
    });
  }

  static async me(filter: SafetyQuizFilter, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    const weekOf = optionalWeekStart(filter.week_of);
    const result = await db.safety_quiz_results.findOne({
      where: {
        userId: requireUserId(currentUser),
        ...(weekOf ? { week_of: weekOf } : {}),
      },
      order: [['completed_at', 'desc']],
    });

    return {
      completed: Boolean(result),
      result: result ? toDto(result) : null,
    };
  }

  static async completion(filter: SafetyQuizFilter, currentUser?: CurrentUser) {
    assertCanReadCompletion(currentUser);
    const weekOf = optionalWeekStart(filter.week_of);
    const staffUsers = await db.users.findAll({
      where: {
        disabled: false,
        ...(await staffScopeWhere(currentUser)),
      },
      include: [
        {
          model: db.roles,
          as: 'app_role',
          required: true,
          where: { name: REPORT_STAFF_ROLES },
        },
        {
          model: db.file,
          as: 'avatar',
          required: false,
        },
        {
          model: db.organizations,
          as: 'organizations',
          required: false,
          attributes: ['id', 'name', 'logo'],
        },
        {
          model: db.schools,
          as: 'school',
          required: false,
          attributes: ['id', 'name', 'logo'],
        },
        {
          model: db.campuses,
          as: 'campus',
          required: false,
          attributes: ['id', 'name', 'logo'],
        },
        {
          model: db.classes,
          as: 'class',
          required: false,
          attributes: ['id', 'name', 'logo'],
        },
      ],
      order: [
        ['lastName', 'asc'],
        ['firstName', 'asc'],
        ['email', 'asc'],
      ],
    });

    const userIds = staffUsers.map((user) => user.id);
    const results = userIds.length > 0
      ? await db.safety_quiz_results.findAll({
          where: {
            userId: userIds,
            ...(weekOf ? { week_of: weekOf } : {}),
          },
          order: [['completed_at', 'desc']],
        })
      : [];
    const resultByUser = latestResultByUser(results);
    const rows = staffUsers.map((user) => {
      const result = resultByUser.get(user.id);
      const tenant = tenantOf(user);
      return {
        userId: user.id,
        name: displayNameOf(user),
        email: user.email,
        avatar: avatarOf(user),
        tenantName: tenant.tenantName,
        tenantLogo: tenant.tenantLogo,
        role: user.app_role?.name ?? null,
        status: result ? 'complete' : 'pending',
        result: result ? toDto(result) : null,
      };
    });
    const completedCount = rows.filter((row) => row.status === 'complete').length;

    return {
      summary: {
        totalStaff: rows.length,
        completedCount,
        pendingCount: Math.max(rows.length - completedCount, 0),
        completionRate: rows.length > 0
          ? Math.round((completedCount / rows.length) * 100)
          : 0,
      },
      rows,
    };
  }
}

export default SafetyQuizResultsService;
