import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import ValidationError from '@/shared/errors/validation';
import { getOrganizationIdOrGlobal } from '@/services/shared/access';
import type { GuardianStudents } from '@/db/models/guardian_students';
import type { CurrentUser } from '@/db/api/types';

interface LinkInput {
  guardianId?: string;
  studentId?: string;
  relationship?: string | null;
}

interface GuardianStudentsFilter {
  studentId?: string;
  guardianId?: string;
}

function orgFilter(currentUser?: CurrentUser): { organizationId?: string } {
  const organizationId = getOrganizationIdOrGlobal(currentUser);
  return organizationId ? { organizationId } : {};
}

function toDto(row: GuardianStudents) {
  const plain = row.get({ plain: true }) as Record<string, unknown> & {
    guardian?: { id?: string; firstName?: string; lastName?: string } | null;
    student?: { id?: string; firstName?: string; lastName?: string } | null;
  };
  return {
    id: plain.id,
    guardianId: plain.guardianId,
    studentId: plain.studentId,
    relationship: plain.relationship,
    guardian: plain.guardian
      ? {
          id: plain.guardian.id,
          firstName: plain.guardian.firstName,
          lastName: plain.guardian.lastName,
        }
      : null,
    student: plain.student
      ? {
          id: plain.student.id,
          firstName: plain.student.firstName,
          lastName: plain.student.lastName,
        }
      : null,
  };
}

class GuardianStudentsService {
  /** Idempotent link of a guardian to a student (tenant-scoped). */
  static async link(data: LinkInput, currentUser?: CurrentUser) {
    if (!data?.guardianId || !data?.studentId) {
      throw new ValidationError();
    }
    const guardianId = data.guardianId;
    const studentId = data.studentId;
    const where = orgFilter(currentUser);

    return withTransaction(async (transaction) => {
      const existing = await db.guardian_students.findOne({
        where: { guardianId, studentId, ...where },
        transaction,
      });
      if (existing) {
        if (data.relationship !== undefined) {
          await existing.update(
            { relationship: data.relationship || null, updatedById: currentUser?.id ?? null },
            { transaction },
          );
        }
        return toDto(existing);
      }
      const row = await db.guardian_students.create(
        {
          guardianId,
          studentId,
          relationship: data.relationship || null,
          organizationId: where.organizationId ?? null,
          createdById: currentUser?.id ?? null,
          updatedById: currentUser?.id ?? null,
        },
        { transaction },
      );
      return toDto(row);
    });
  }

  static async unlink(id: string, currentUser?: CurrentUser): Promise<void> {
    const where = orgFilter(currentUser);
    await withTransaction(async (transaction) => {
      const row = await db.guardian_students.findOne({
        where: { id, ...where },
        transaction,
      });
      if (!row) {
        throw new ValidationError();
      }
      await row.destroy({ transaction });
    });
  }

  /** Lists links, filtered by studentId or guardianId, with the other party loaded. */
  static async list(filter: GuardianStudentsFilter, currentUser?: CurrentUser) {
    const where: Record<string, unknown> = { ...orgFilter(currentUser) };
    if (filter.studentId) where.studentId = filter.studentId;
    if (filter.guardianId) where.guardianId = filter.guardianId;

    const rows = await db.guardian_students.findAll({
      where,
      include: [
        { model: db.users, as: 'guardian' },
        { model: db.users, as: 'student' },
      ],
      order: [['createdAt', 'desc']],
    });

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

export default GuardianStudentsService;
