import { nullableString } from '@/services/shared/validate';
import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import { resolvePagination } from '@/shared/constants/pagination';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import {
  WALKTHROUGH_MANAGER_ROLE_NAMES,
  WALKTHROUGH_TENANT_WIDE_ROLE_NAMES,
} from '@/shared/constants/walkthrough';
import {
  getOrganizationIdOrGlobal,
  hasGlobalAccess,
  hasRoleAccess,
  requireUserId,
} from '@/services/shared/access';
import type { WalkthroughCheckins } from '@/db/models/walkthrough_checkins';
import type { CurrentUser } from '@/db/api/types';
import type {
  WalkthroughInput,
  WalkthroughFilter,
} from '@/services/walkthrough_checkins.types';

const REQUIRED_STRING_FIELDS = [
  'teacher_name',
  'classroom',
  'director_name',
  'check_in_date',
  'check_in_time',
] as const;

const RATING_FIELDS = [
  'attitude_rating',
  'classroom_management_rating',
  'cleanliness_rating',
  'vibes_rating',
  'team_dynamics_rating',
  'emergency_exit_rating',
  'lesson_plan_rating',
] as const;

/** Walkthrough scopes by the staff member's campus only (never the user's own
 * campusId), so this keeps its module-local resolver. */
function getCampusId(currentUser?: CurrentUser): string | null {
  const staff = currentUser?.staff_user;
  if (Array.isArray(staff) && staff[0]?.campusId) {
    return staff[0].campusId;
  }

  return null;
}

function assertCanManage(currentUser?: CurrentUser): void {
  if (!currentUser?.id) {
    throw new ForbiddenError();
  }

  if (hasGlobalAccess(currentUser)) {
    return;
  }

  const organizationId = getOrganizationIdOrGlobal(currentUser);
  if (organizationId && hasRoleAccess(currentUser, WALKTHROUGH_MANAGER_ROLE_NAMES)) {
    return;
  }

  throw new ForbiddenError();
}

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

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

  const missingRating = RATING_FIELDS.some(
    (field) => typeof data[field] !== 'number' || !Number.isFinite(data[field]),
  );

  if (missingString || missingRating) {
    throw new ValidationError();
  }
}

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

  return {
    id: plain.id,
    teacher_name: plain.teacher_name,
    classroom: plain.classroom,
    director_name: plain.director_name,
    check_in_date: plain.check_in_date,
    check_in_time: plain.check_in_time,
    attitude_rating: plain.attitude_rating,
    attitude_comment: plain.attitude_comment,
    classroom_management_rating: plain.classroom_management_rating,
    classroom_management_comment: plain.classroom_management_comment,
    cleanliness_rating: plain.cleanliness_rating,
    cleanliness_comment: plain.cleanliness_comment,
    vibes_rating: plain.vibes_rating,
    vibes_comment: plain.vibes_comment,
    team_dynamics_rating: plain.team_dynamics_rating,
    team_dynamics_comment: plain.team_dynamics_comment,
    emergency_exit_rating: plain.emergency_exit_rating,
    emergency_exit_comment: plain.emergency_exit_comment,
    lesson_plan_rating: plain.lesson_plan_rating,
    lesson_plan_comment: plain.lesson_plan_comment,
    overall_notes: plain.overall_notes,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

/** Restricts non-tenant-wide roles to their own campus. Uses the module-local
 * `getCampusId` (staff-campus only), so it stays local. */
function campusScope(currentUser?: CurrentUser): { campusId?: string } {
  if (hasRoleAccess(currentUser, WALKTHROUGH_TENANT_WIDE_ROLE_NAMES)) {
    return {};
  }

  const campusId = getCampusId(currentUser);
  return campusId ? { campusId } : {};
}

class WalkthroughCheckinsService {
  static async list(filter: WalkthroughFilter, currentUser?: CurrentUser) {
    assertCanManage(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

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

    const result = await db.walkthrough_checkins.findAndCountAll({
      where: {
        ...orgFilter,
        ...campusScope(currentUser),
        ...(filter.teacher_name ? { teacher_name: filter.teacher_name } : {}),
      },
      order: [
        ['check_in_date', 'desc'],
        ['createdAt', 'desc'],
      ],
      limit,
      offset,
    });

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

  static async create(data: WalkthroughInput, currentUser?: CurrentUser) {
    assertCanManage(currentUser);
    assertValidCheckin(data);

    const organizationId = getOrganizationIdOrGlobal(currentUser);

    return withTransaction(async (transaction) => {
      const created = await db.walkthrough_checkins.create(
        {
          teacher_name: data.teacher_name.trim(),
          classroom: data.classroom.trim(),
          director_name: data.director_name.trim(),
          check_in_date: data.check_in_date.trim(),
          check_in_time: data.check_in_time.trim(),
          attitude_rating: data.attitude_rating,
          attitude_comment: nullableString(data.attitude_comment),
          classroom_management_rating: data.classroom_management_rating,
          classroom_management_comment: nullableString(
            data.classroom_management_comment,
          ),
          cleanliness_rating: data.cleanliness_rating,
          cleanliness_comment: nullableString(data.cleanliness_comment),
          vibes_rating: data.vibes_rating,
          vibes_comment: nullableString(data.vibes_comment),
          team_dynamics_rating: data.team_dynamics_rating,
          team_dynamics_comment: nullableString(data.team_dynamics_comment),
          emergency_exit_rating: data.emergency_exit_rating,
          emergency_exit_comment: nullableString(data.emergency_exit_comment),
          lesson_plan_rating: data.lesson_plan_rating,
          lesson_plan_comment: nullableString(data.lesson_plan_comment),
          overall_notes: nullableString(data.overall_notes),
          organizationId: organizationId ?? undefined,
          campusId: getCampusId(currentUser) ?? undefined,
          createdById: requireUserId(currentUser),
          updatedById: currentUser?.id ?? null,
        },
        { transaction },
      );

      return toDto(created);
    });
  }

  static async remove(id: string, currentUser?: CurrentUser) {
    assertCanManage(currentUser);

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

    const deletedCount = await db.walkthrough_checkins.destroy({
      where: {
        id,
        ...orgFilter,
        ...campusScope(currentUser),
      },
    });

    return { deletedCount };
  }
}

export default WalkthroughCheckinsService;
