import { clampLimit, requiredIsoDate } from '@/services/shared/validate';
import { Op, literal } from 'sequelize';
import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import {
  CAMPUS_ATTENDANCE_DEFAULT_LIMIT,
  CAMPUS_ATTENDANCE_MAX_LIMIT,
  normalizeCampusKey,
} from '@/shared/constants/campus-attendance';
import { ROLE_SCOPES } from '@/shared/constants/roles';
import { resolvePagination } from '@/shared/constants/pagination';
import type { CampusAttendanceConfig } from '@/db/models/campus_attendance_config';
import type { CampusAttendanceSummaries } from '@/db/models/campus_attendance_summaries';
import type { CurrentUser } from '@/db/api/types';
import type {
  CampusAttendanceFilter,
  ConfigInput,
  SummaryInput,
} from '@/services/campus_attendance.types';
import {
  assertAuthenticatedTenantUser,
  getCampusId,
  getRoleScope,
  getSchoolId,
  hasFeaturePermission,
  hasGlobalAccess,
  requireOrganizationId,
  requireUserId,
  getDisplayName,
} from '@/services/shared/access';
import { FEATURE_PERMISSIONS } from '@/shared/constants/product-permissions';

/** UUIDs are inlined into a SQL literal subquery; reject anything that is not one. */
const CA_UUID_RE =
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

function getEmbeddedCampusKey(currentUser?: CurrentUser): string | null {
  return (
    normalizeCampusKey(currentUser?.campus?.code) ||
    normalizeCampusKey(currentUser?.campus?.name) ||
    null
  );
}

async function getCurrentUserCampusKey(currentUser?: CurrentUser): Promise<string | null> {
  const embeddedCampusKey = getEmbeddedCampusKey(currentUser);
  if (embeddedCampusKey) {
    return embeddedCampusKey;
  }

  const campusId = getCampusId(currentUser);
  if (!campusId) {
    return null;
  }

  const campus = await db.campuses.findOne({
    attributes: ['code', 'name'],
    where: {
      id: campusId,
      organizationId: requireOrganizationId(currentUser),
    },
  });

  if (!campus) {
    return null;
  }

  const plain = campus.get({ plain: true }) as {
    code?: string | null;
    name?: string | null;
  };

  return normalizeCampusKey(plain.code) || normalizeCampusKey(plain.name) || null;
}

function canManageCampusAttendance(currentUser?: CurrentUser): boolean {
  return hasFeaturePermission(
    currentUser,
    FEATURE_PERMISSIONS.FILL_ATTENDANCE,
  );
}

function assertCanManageCampusAttendance(currentUser?: CurrentUser): void {
  assertAuthenticatedTenantUser(currentUser);

  if (canManageCampusAttendance(currentUser)) {
    return;
  }

  throw new ForbiddenError();
}

function campusKeyFromRoute(value: unknown): string {
  const campusKey = normalizeCampusKey(value);

  if (!campusKey) {
    throw new ValidationError();
  }

  return campusKey;
}

/** True for roles whose reach is the whole organization (global + org scope). */
function isOrgWide(currentUser?: CurrentUser): boolean {
  return (
    hasGlobalAccess(currentUser) ||
    getRoleScope(currentUser) === ROLE_SCOPES.ORGANIZATION
  );
}

/**
 * Subquery of the campus_keys a school owns. `campus_key` is the normalized
 * campus code, and the seeded product codes are already lowercase slugs, so
 * `LOWER("code")` matches the stored key. Inlines a validated UUID only.
 */
function schoolCampusKeySubquery(schoolId: string) {
  if (!CA_UUID_RE.test(schoolId)) {
    throw new ForbiddenError();
  }
  return literal(
    `(SELECT LOWER("code") FROM "campuses" WHERE "schoolId" = '${schoolId}' AND "deletedAt" IS NULL)`,
  );
}

/** Whether `campusKey` belongs to a campus in the user's school (parameterized). */
async function isCampusKeyInSchool(
  campusKey: string,
  schoolId: string,
  organizationId: string,
): Promise<boolean> {
  const count = await db.campuses.count({
    where: {
      schoolId,
      organizationId,
      [Op.and]: [
        db.sequelize.where(
          db.sequelize.fn('LOWER', db.sequelize.col('code')),
          campusKey,
        ),
      ],
    },
  });
  return count > 0;
}

/** Asserts the user may read/write the given campus_key, by scope tier. */
async function assertCanAccessCampusKey(
  campusKey: string,
  currentUser?: CurrentUser,
): Promise<void> {
  if (isOrgWide(currentUser)) {
    return;
  }

  if (getRoleScope(currentUser) === ROLE_SCOPES.SCHOOL) {
    const schoolId = getSchoolId(currentUser);
    if (
      schoolId &&
      (await isCampusKeyInSchool(
        campusKey,
        schoolId,
        requireOrganizationId(currentUser),
      ))
    ) {
      return;
    }
    throw new ForbiddenError();
  }

  const currentCampusKey = await getCurrentUserCampusKey(currentUser);
  if (currentCampusKey && currentCampusKey === campusKey) {
    return;
  }

  throw new ForbiddenError();
}

/** Resolves the campus_key scope by tier, asserting access along the way. */
async function campusKeyScope(
  filter: CampusAttendanceFilter,
  currentUser?: CurrentUser,
): Promise<{ campus_key?: string | { [Op.in]: ReturnType<typeof literal> } }> {
  const requestedCampusKey = normalizeCampusKey(filter?.campusKey);

  if (requestedCampusKey) {
    await assertCanAccessCampusKey(requestedCampusKey, currentUser);
    return { campus_key: requestedCampusKey };
  }

  if (isOrgWide(currentUser)) {
    return {};
  }

  // School scope: every campus in the current school.
  if (getRoleScope(currentUser) === ROLE_SCOPES.SCHOOL) {
    const schoolId = getSchoolId(currentUser);
    if (!schoolId) {
      throw new ForbiddenError();
    }
    return { campus_key: { [Op.in]: schoolCampusKeySubquery(schoolId) } };
  }

  const currentCampusKey = await getCurrentUserCampusKey(currentUser);

  if (!currentCampusKey) {
    throw new ForbiddenError();
  }

  return { campus_key: currentCampusKey };
}

function dateRange(filter: CampusAttendanceFilter): {
  attendance_date?: { [Op.gte]?: string; [Op.lte]?: string };
} {
  const start = filter.startDate ? requiredIsoDate(filter.startDate) : null;
  const end = filter.endDate ? requiredIsoDate(filter.endDate) : null;

  if (!start && !end) {
    return {};
  }

  return {
    attendance_date: {
      ...(start ? { [Op.gte]: start } : {}),
      ...(end ? { [Op.lte]: end } : {}),
    },
  };
}

function requiredNonNegativeInteger(value: unknown): number {
  if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
    throw new ValidationError();
  }

  return value;
}

function optionalText(value: unknown): string | null {
  if (typeof value !== 'string' || value.trim().length === 0) {
    return null;
  }

  return value.trim();
}

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

  const totalEnrolled = requiredNonNegativeInteger(data.total_enrolled);
  const totalPresent = requiredNonNegativeInteger(data.total_present);
  const totalAbsent = requiredNonNegativeInteger(data.total_absent);
  const totalTardy = requiredNonNegativeInteger(data.total_tardy ?? 0);

  if (
    totalEnrolled <= 0 ||
    totalPresent > totalEnrolled ||
    totalAbsent > totalEnrolled ||
    totalTardy > totalEnrolled
  ) {
    throw new ValidationError();
  }

  return {
    total_enrolled: totalEnrolled,
    total_present: totalPresent,
    total_absent: totalAbsent,
    total_tardy: totalTardy,
    attendance_percentage: ((totalPresent / totalEnrolled) * 100).toFixed(2),
    notes: optionalText(data.notes),
  };
}

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

  return {
    id: plain.id,
    campus_key: plain.campus_key,
    attendance_link: plain.attendance_link,
    updated_by_label: plain.updated_by_label,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

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

  return {
    id: plain.id,
    campus_key: plain.campus_key,
    date: plain.attendance_date,
    total_enrolled: plain.total_enrolled,
    total_present: plain.total_present,
    total_absent: plain.total_absent,
    total_tardy: plain.total_tardy,
    attendance_percentage: Number(plain.attendance_percentage),
    recorded_by_label: plain.recorded_by_label,
    notes: plain.notes,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class CampusAttendanceService {
  static async listConfigs(
    filter: CampusAttendanceFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

    const result = await db.campus_attendance_config.findAndCountAll({
      where: {
        organizationId: requireOrganizationId(currentUser),
        ...(await campusKeyScope(filter, currentUser)),
      },
      order: [['campus_key', 'asc']],
      limit,
      offset,
    });

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

  static async upsertConfig(
    campusKeyParam: unknown,
    data: ConfigInput,
    currentUser?: CurrentUser,
  ) {
    assertCanManageCampusAttendance(currentUser);

    const campusKey = campusKeyFromRoute(campusKeyParam);
    await assertCanAccessCampusKey(campusKey, currentUser);

    if (!data || typeof data !== 'object' || Array.isArray(data)) {
      throw new ValidationError();
    }

    const payload = {
      campus_key: campusKey,
      attendance_link: optionalText(data.attendance_link),
      updated_by_label: getDisplayName(currentUser),
      organizationId: requireOrganizationId(currentUser),
      campusId: getCampusId(currentUser),
      updatedById: currentUser?.id ?? null,
    };
    return withTransaction(async (transaction) => {
      const existing = await db.campus_attendance_config.findOne({
        where: {
          organizationId: requireOrganizationId(currentUser),
          campus_key: campusKey,
        },
        transaction,
      });
      const saved = existing
        ? await existing.update(payload, { transaction })
        : await db.campus_attendance_config.create(
            { ...payload, createdById: requireUserId(currentUser) },
            { transaction },
          );

      return toConfigDto(saved);
    });
  }

  static async listSummaries(
    filter: CampusAttendanceFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);

    const result = await db.campus_attendance_summaries.findAndCountAll({
      where: {
        organizationId: requireOrganizationId(currentUser),
        ...(await campusKeyScope(filter, currentUser)),
        ...dateRange(filter),
      },
      limit: clampLimit(filter.limit, CAMPUS_ATTENDANCE_DEFAULT_LIMIT, CAMPUS_ATTENDANCE_MAX_LIMIT),
      order: [
        ['attendance_date', 'desc'],
        ['campus_key', 'asc'],
      ],
    });

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

  static async upsertSummary(
    campusKeyParam: unknown,
    dateParam: unknown,
    data: SummaryInput,
    currentUser?: CurrentUser,
  ) {
    assertCanManageCampusAttendance(currentUser);

    const campusKey = campusKeyFromRoute(campusKeyParam);
    const attendanceDate = requiredIsoDate(dateParam);
    await assertCanAccessCampusKey(campusKey, currentUser);

    const validatedSummary = validateSummary(data);
    const payload = {
      ...validatedSummary,
      campus_key: campusKey,
      attendance_date: attendanceDate,
      recorded_by_label: getDisplayName(currentUser),
      organizationId: requireOrganizationId(currentUser),
      campusId: getCampusId(currentUser),
      updatedById: currentUser?.id ?? null,
    };
    return withTransaction(async (transaction) => {
      const existing = await db.campus_attendance_summaries.findOne({
        where: {
          organizationId: requireOrganizationId(currentUser),
          campus_key: campusKey,
          attendance_date: attendanceDate,
        },
        transaction,
      });
      const saved = existing
        ? await existing.update(payload, { transaction })
        : await db.campus_attendance_summaries.create(
            { ...payload, createdById: requireUserId(currentUser) },
            { transaction },
          );

      return toSummaryDto(saved);
    });
  }
}

export default CampusAttendanceService;
