import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { interests } from '@/lib/db/schema/interests';
import { yachts } from '@/lib/db/schema/yachts';
import { berths, berthRecommendations } from '@/lib/db/schema/berths';
import { NotFoundError } from '@/lib/errors';
import { createAuditLog, type AuditMeta } from '@/lib/audit';

// ─── Score a single berth ─────────────────────────────────────────────────────

function scoreBerth(
  berth: typeof berths.$inferSelect,
  yachtLengthFt: number | null,
  yachtWidthFt: number | null,
  yachtDraftFt: number | null,
): { score: number; reasons: Record<string, number> } {
  const reasons: Record<string, number> = {};
  const weights: number[] = [];

  if (yachtLengthFt && berth.lengthFt) {
    const berthLen = parseFloat(berth.lengthFt);
    if (berthLen >= yachtLengthFt) {
      // Prefer berths that are not too oversized (within 20% extra is ideal)
      const score =
        berthLen <= yachtLengthFt * 1.2
          ? 100
          : Math.max(50, 100 - (berthLen / yachtLengthFt - 1.2) * 100);
      reasons['length_fit'] = Math.round(score);
      weights.push(score);
    } else {
      // Berth too small
      reasons['length_fit'] = 0;
      weights.push(0);
    }
  }

  if (yachtWidthFt && berth.widthFt) {
    const berthWidth = parseFloat(berth.widthFt);
    if (berthWidth >= yachtWidthFt) {
      const score =
        berthWidth <= yachtWidthFt * 1.3
          ? 100
          : Math.max(40, 100 - (berthWidth / yachtWidthFt - 1.3) * 80);
      reasons['beam_fit'] = Math.round(score);
      weights.push(score);
    } else {
      reasons['beam_fit'] = 0;
      weights.push(0);
    }
  }

  if (yachtDraftFt && berth.draftFt) {
    const berthDraft = parseFloat(berth.draftFt);
    if (berthDraft >= yachtDraftFt) {
      const score = 100;
      reasons['draft_fit'] = score;
      weights.push(score);
    } else {
      reasons['draft_fit'] = 0;
      weights.push(0);
    }
  }

  if (weights.length === 0) {
    return { score: 50, reasons: { no_dimensions: 50 } };
  }

  const score = Math.round(weights.reduce((a, b) => a + b, 0) / weights.length);
  return { score, reasons };
}

// ─── Generate Recommendations ─────────────────────────────────────────────────

export async function generateRecommendations(interestId: string, portId: string, meta: AuditMeta) {
  const interest = await db.query.interests.findFirst({
    where: and(eq(interests.id, interestId), eq(interests.portId, portId)),
  });
  if (!interest) throw new NotFoundError('Interest');

  // Read yacht dimensions from the yachts table via interest.yachtId (PR 9)
  let yachtLengthFt: number | null = null;
  let yachtWidthFt: number | null = null;
  let yachtDraftFt: number | null = null;

  if (interest.yachtId) {
    const yacht = await db.query.yachts.findFirst({
      where: and(eq(yachts.id, interest.yachtId), eq(yachts.portId, portId)),
    });
    if (yacht) {
      yachtLengthFt = yacht.lengthFt ? parseFloat(yacht.lengthFt) : null;
      yachtWidthFt = yacht.widthFt ? parseFloat(yacht.widthFt) : null;
      yachtDraftFt = yacht.draftFt ? parseFloat(yacht.draftFt) : null;
    }
  }

  // Get all available berths for the port
  const availableBerths = await db
    .select()
    .from(berths)
    .where(and(eq(berths.portId, portId), eq(berths.status, 'available')));

  // Score each berth
  const scored = availableBerths.map((berth) => {
    const { score, reasons } = scoreBerth(berth, yachtLengthFt, yachtWidthFt, yachtDraftFt);
    return { berth, score, reasons };
  });

  // Sort by score and take top 10
  scored.sort((a, b) => b.score - a.score);
  const top10 = scored.slice(0, 10);

  // Delete existing AI recommendations for this interest
  await db
    .delete(berthRecommendations)
    .where(
      and(eq(berthRecommendations.interestId, interestId), eq(berthRecommendations.source, 'ai')),
    );

  // Insert new recommendations
  if (top10.length > 0) {
    await db.insert(berthRecommendations).values(
      top10.map(({ berth, score, reasons }) => ({
        interestId,
        berthId: berth.id,
        matchScore: String(score),
        matchReasons: reasons,
        source: 'ai' as const,
        createdBy: meta.userId,
      })),
    );
  }

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'create',
    entityType: 'berth_recommendation',
    entityId: interestId,
    metadata: { type: 'ai_generated', count: top10.length },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  return listRecommendations(interestId, portId);
}

// ─── List Recommendations ─────────────────────────────────────────────────────

export async function listRecommendations(interestId: string, portId: string) {
  // Verify the interest belongs to the caller's port. Without this gate,
  // any user with `interests:view` could pass a foreign-port interestId
  // and receive that tenant's recommended berths (mooring numbers,
  // dimensions, status - operational data they should not see).
  const interest = await db.query.interests.findFirst({
    where: and(eq(interests.id, interestId), eq(interests.portId, portId)),
  });
  if (!interest) throw new NotFoundError('Interest');

  const rows = await db
    .select({
      id: berthRecommendations.id,
      interestId: berthRecommendations.interestId,
      berthId: berthRecommendations.berthId,
      matchScore: berthRecommendations.matchScore,
      matchReasons: berthRecommendations.matchReasons,
      source: berthRecommendations.source,
      createdBy: berthRecommendations.createdBy,
      createdAt: berthRecommendations.createdAt,
      mooringNumber: berths.mooringNumber,
      area: berths.area,
      status: berths.status,
      lengthFt: berths.lengthFt,
      widthFt: berths.widthFt,
      draftFt: berths.draftFt,
    })
    .from(berthRecommendations)
    .innerJoin(berths, eq(berthRecommendations.berthId, berths.id))
    .where(and(eq(berthRecommendations.interestId, interestId), eq(berths.portId, portId)))
    .orderBy(berthRecommendations.matchScore);

  return rows.reverse(); // highest score first
}

// ─── Add Manual Recommendation ────────────────────────────────────────────────

export async function addManualRecommendation(
  interestId: string,
  portId: string,
  berthId: string,
  meta: AuditMeta,
) {
  const interest = await db.query.interests.findFirst({
    where: and(eq(interests.id, interestId), eq(interests.portId, portId)),
  });
  if (!interest) throw new NotFoundError('Interest');

  const berth = await db.query.berths.findFirst({
    where: and(eq(berths.id, berthId), eq(berths.portId, portId)),
  });
  if (!berth) throw new NotFoundError('Berth');

  const [rec] = await db
    .insert(berthRecommendations)
    .values({
      interestId,
      berthId,
      source: 'manual',
      createdBy: meta.userId,
    })
    .returning();

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'create',
    entityType: 'berth_recommendation',
    entityId: rec!.id,
    metadata: { type: 'manual', interestId, berthId },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  return rec!;
}
