import { and, desc, eq, isNull, or, sql } from "drizzle-orm";

import { getDatabase } from "@/modules/core/server/db";
import { admissions, beds, elders, rooms, systemIncidents } from "@/modules/core/server/schema";
import type { Admission, FacilityBed, Room, SystemIncident } from "@/modules/core/types";
import type { Elder } from "@/modules/elders/types";

function iso(value: Date): string {
  return value.toISOString();
}

type ActiveBedLabel = {
  bed: string;
  bedId: string;
  room: string;
};

function toElder(row: typeof elders.$inferSelect, activeBed?: ActiveBedLabel): Elder {
  return {
    id: row.id,
    organizationId: row.organizationId,
    name: row.name,
    gender: row.gender,
    age: row.age,
    careLevel: row.careLevel,
    room: activeBed?.room ?? "",
    bed: activeBed?.bed ?? "",
    bedId: activeBed?.bedId,
    status: row.status,
    primaryContact: row.primaryContact,
    phone: row.phone,
    medicalNotes: row.medicalNotes,
    createdAt: iso(row.createdAt),
    updatedAt: iso(row.updatedAt),
  };
}

export async function listOperationalElders(organizationId?: string): Promise<Elder[]> {
  const database = getDatabase();
  const [elderRows, activeAdmissionRows] = await Promise.all([
    database
      .select()
      .from(elders)
      .where(organizationId ? eq(elders.organizationId, organizationId) : sql`true`)
      .orderBy(desc(elders.createdAt)),
    database
      .select({
        bedCode: beds.code,
        bedId: admissions.bedId,
        elderId: admissions.elderId,
        roomName: rooms.name,
      })
      .from(admissions)
      .innerJoin(beds, eq(beds.id, admissions.bedId))
      .innerJoin(rooms, eq(rooms.id, beds.roomId))
      .where(
        organizationId
          ? and(eq(admissions.organizationId, organizationId), eq(admissions.status, "active"))
          : eq(admissions.status, "active"),
      )
      .orderBy(desc(admissions.admittedAt)),
  ]);
  const bedLabelByElderId = new Map<string, ActiveBedLabel>();
  for (const row of activeAdmissionRows) {
    if (bedLabelByElderId.has(row.elderId)) {
      continue;
    }

    bedLabelByElderId.set(row.elderId, {
      room: row.roomName,
      bed: row.bedCode,
      bedId: row.bedId,
    });
  }

  return elderRows.map((row) => toElder(row, bedLabelByElderId.get(row.id)));
}

export async function listOperationalRooms(organizationId?: string): Promise<Room[]> {
  const database = getDatabase();
  const [roomRows, bedRows] = await Promise.all([
    database
      .select()
      .from(rooms)
      .where(organizationId ? eq(rooms.organizationId, organizationId) : sql`true`)
      .orderBy(desc(rooms.createdAt)),
    database
      .select()
      .from(beds)
      .where(organizationId ? eq(beds.organizationId, organizationId) : sql`true`),
  ]);
  const bedCounts = new Map<string, { occupied: number; total: number }>();
  for (const bed of bedRows) {
    const current = bedCounts.get(bed.roomId) ?? { occupied: 0, total: 0 };
    current.total += 1;
    if (bed.status === "occupied") {
      current.occupied += 1;
    }
    bedCounts.set(bed.roomId, current);
  }

  return roomRows.map((room) => {
    const counts = bedCounts.get(room.id) ?? { occupied: 0, total: 0 };
    return {
      id: room.id,
      organizationId: room.organizationId,
      name: room.name,
      code: room.code,
      capacity: room.capacity,
      status: room.status,
      bedCount: counts.total,
      occupiedBedCount: counts.occupied,
      createdAt: iso(room.createdAt),
      updatedAt: iso(room.updatedAt),
    };
  });
}

export async function listOperationalBeds(organizationId?: string): Promise<FacilityBed[]> {
  const database = getDatabase();
  const bedRows = await database
    .select({
      bed: beds,
      currentElderName: sql<string | null>`(
        select ${elders.name}
        from ${admissions}
        inner join ${elders} on ${elders.id} = ${admissions.elderId}
        where ${admissions.bedId} = ${beds.id} and ${admissions.status} = 'active'
        order by ${admissions.admittedAt} desc
        limit 1
      )`,
      roomName: rooms.name,
    })
    .from(beds)
    .innerJoin(rooms, eq(rooms.id, beds.roomId))
    .where(organizationId ? eq(beds.organizationId, organizationId) : sql`true`)
    .orderBy(desc(beds.createdAt));

  return bedRows.map((row) => ({
    id: row.bed.id,
    organizationId: row.bed.organizationId,
    roomId: row.bed.roomId,
    roomName: row.roomName,
    code: row.bed.code,
    status: row.bed.status,
    notes: row.bed.notes,
    currentElderName: row.currentElderName ?? undefined,
    createdAt: iso(row.bed.createdAt),
    updatedAt: iso(row.bed.updatedAt),
  }));
}

export async function listOperationalAdmissions(organizationId?: string): Promise<Admission[]> {
  const database = getDatabase();
  const admissionRows = await database
    .select({
      admission: admissions,
      bedCode: beds.code,
      elderName: elders.name,
      roomName: rooms.name,
    })
    .from(admissions)
    .innerJoin(elders, eq(elders.id, admissions.elderId))
    .innerJoin(beds, eq(beds.id, admissions.bedId))
    .innerJoin(rooms, eq(rooms.id, beds.roomId))
    .where(organizationId ? eq(admissions.organizationId, organizationId) : sql`true`)
    .orderBy(desc(admissions.admittedAt));

  return admissionRows.map((row) => ({
    id: row.admission.id,
    organizationId: row.admission.organizationId,
    elderId: row.admission.elderId,
    elderName: row.elderName,
    bedId: row.admission.bedId,
    bedCode: row.bedCode,
    roomName: row.roomName,
    status: row.admission.status,
    admittedAt: iso(row.admission.admittedAt),
    dischargedAt: row.admission.dischargedAt ? iso(row.admission.dischargedAt) : undefined,
    notes: row.admission.notes,
  }));
}

export async function listOperationalIncidents(organizationId?: string): Promise<SystemIncident[]> {
  const database = getDatabase();
  const incidentRows = await database
    .select()
    .from(systemIncidents)
    .where(organizationId ? or(isNull(systemIncidents.organizationId), eq(systemIncidents.organizationId, organizationId)) : sql`true`)
    .orderBy(desc(systemIncidents.createdAt));

  return incidentRows.map((incident) => ({
    id: incident.id,
    organizationId: incident.organizationId ?? undefined,
    severity: incident.severity,
    status: incident.status,
    title: incident.title,
    description: incident.description,
    source: incident.source,
    createdAt: iso(incident.createdAt),
    updatedAt: iso(incident.updatedAt),
  }));
}
