/**
 * recommendations.service yacht dimensions source integration tests.
 *
 * Covers:
 *  - generateRecommendations reads yacht dimensions from yachts table (not client) when interest.yachtId is set
 *  - Falls back to null dimensions when interest has no yacht linked
 *  - Correctly guards against cross-tenant access (yacht.portId must match)
 *  - Recommendations are scored based on yacht dimensions from yachts table
 *
 * Uses dynamic imports so env is loaded before service modules touch `db`.
 */
import { describe, it, expect, beforeAll } from 'vitest';
import { db } from '@/lib/db';
import { interests } from '@/lib/db/schema/interests';

describe('recommendations.service - yacht dimensions source', () => {
  let generateRecommendations: typeof import('@/lib/services/recommendations').generateRecommendations;

  let makePort: typeof import('../helpers/factories').makePort;

  let makeClient: typeof import('../helpers/factories').makeClient;

  let makeYacht: typeof import('../helpers/factories').makeYacht;

  let makeBerth: typeof import('../helpers/factories').makeBerth;

  let makeAuditMeta: typeof import('../helpers/factories').makeAuditMeta;

  beforeAll(async () => {
    const svc = await import('@/lib/services/recommendations');
    generateRecommendations = svc.generateRecommendations;

    const factories = await import('../helpers/factories');
    makePort = factories.makePort;
    makeClient = factories.makeClient;
    makeYacht = factories.makeYacht;
    makeBerth = factories.makeBerth;
    makeAuditMeta = factories.makeAuditMeta;
  });

  it('reads yacht dimensions from yachts table (not client) when interest.yachtId is set', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });
    const yacht = await makeYacht({
      portId: port.id,
      ownerType: 'client',
      ownerId: client.id,
      overrides: {
        lengthFt: '45',
        widthFt: '14',
        draftFt: '7',
      },
    });

    // Create a berth that matches the yacht dimensions
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const berth = await makeBerth({
      portId: port.id,
      overrides: {
        lengthFt: '50',
        widthFt: '15',
        draftFt: '8',
      },
    });

    // Insert interest with yachtId
    const [interest] = await db
      .insert(interests)
      .values({
        portId: port.id,
        clientId: client.id,
        yachtId: yacht.id,
        pipelineStage: 'open',
      })
      .returning();

    const recommendations = await generateRecommendations(
      interest!.id,
      port.id,
      makeAuditMeta({ portId: port.id }),
    );

    // Should have at least one recommendation with scoring based on yacht dims
    expect(recommendations.length).toBeGreaterThan(0);
    const rec = recommendations[0];
    expect(rec).toBeDefined();
    // length_fit, beam_fit, draft_fit should be in reasons
    const reasons = rec!.matchReasons as Record<string, number>;
    expect(reasons).toHaveProperty('length_fit');
    expect(reasons).toHaveProperty('beam_fit');
    expect(reasons).toHaveProperty('draft_fit');
  });

  it('falls back to null dimensions when interest has no yacht linked', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });

    // Create a berth
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const berth = await makeBerth({
      portId: port.id,
      overrides: {
        lengthFt: '50',
        widthFt: '15',
        draftFt: '8',
      },
    });

    // Insert interest without yachtId
    const [interest] = await db
      .insert(interests)
      .values({
        portId: port.id,
        clientId: client.id,
        yachtId: null,
        pipelineStage: 'open',
      })
      .returning();

    const recommendations = await generateRecommendations(
      interest!.id,
      port.id,
      makeAuditMeta({ portId: port.id }),
    );

    // Should still have recommendations, but with fallback scoring (no_dimensions reason)
    expect(recommendations.length).toBeGreaterThan(0);
    const rec = recommendations[0];
    const reasons = rec!.matchReasons as Record<string, number>;
    // When no yacht dimensions, the score should use the fallback (no_dimensions)
    expect(reasons).toHaveProperty('no_dimensions');
  });

  it('falls back to null dimensions when yacht is cross-tenant (safety)', async () => {
    const port1 = await makePort();
    const port2 = await makePort();
    const client = await makeClient({ portId: port1.id });

    // Create yacht in port2
    const yachtInPort2 = await makeYacht({
      portId: port2.id,
      ownerType: 'client',
      ownerId: client.id,
      overrides: {
        lengthFt: '45',
        widthFt: '14',
        draftFt: '7',
      },
    });

    // Create berth in port1
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const berth = await makeBerth({
      portId: port1.id,
      overrides: {
        lengthFt: '50',
        widthFt: '15',
        draftFt: '8',
      },
    });

    // Insert interest in port1 but with yachtId pointing to port2's yacht
    // This simulates a cross-tenant safety issue
    const [interest] = await db
      .insert(interests)
      .values({
        portId: port1.id,
        clientId: client.id,
        yachtId: yachtInPort2.id,
        pipelineStage: 'open',
      })
      .returning();

    const recommendations = await generateRecommendations(
      interest!.id,
      port1.id, // Asking for port1 recommendations
      makeAuditMeta({ portId: port1.id }),
    );

    // The yacht lookup should fail due to portId mismatch, so fall back to null dims
    expect(recommendations.length).toBeGreaterThan(0);
    const rec = recommendations[0];
    const reasons = rec!.matchReasons as Record<string, number>;
    // Should use no_dimensions fallback because yacht from another port is not found
    expect(reasons).toHaveProperty('no_dimensions');
  });

  it('correctly scores berths based on yacht dimension matches', async () => {
    const port = await makePort();
    const client = await makeClient({ portId: port.id });

    // Create a 40ft yacht
    const yacht = await makeYacht({
      portId: port.id,
      ownerType: 'client',
      ownerId: client.id,
      overrides: {
        lengthFt: '40',
        widthFt: '12',
        draftFt: '6',
      },
    });

    // Create two berths: one that fits perfectly, one that's too small
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const perfectFitBerth = await makeBerth({
      portId: port.id,
      overrides: {
        mooringNumber: 'PERFECT',
        lengthFt: '42', // within 20% of 40
        widthFt: '13', // within 30% of 12
        draftFt: '7', // sufficient for 6
      },
    });

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const tooSmallBerth = await makeBerth({
      portId: port.id,
      overrides: {
        mooringNumber: 'SMALL',
        lengthFt: '35', // smaller than yacht
        widthFt: '10', // smaller than yacht
        draftFt: '5', // smaller than yacht
      },
    });

    // Insert interest with yacht
    const [interest] = await db
      .insert(interests)
      .values({
        portId: port.id,
        clientId: client.id,
        yachtId: yacht.id,
        pipelineStage: 'open',
      })
      .returning();

    const recommendations = await generateRecommendations(
      interest!.id,
      port.id,
      makeAuditMeta({ portId: port.id }),
    );

    // Should have 2 recommendations
    expect(recommendations.length).toBe(2);

    // The first (highest score) should be the perfect-fit berth
    const topRec = recommendations[0];
    expect(topRec!.mooringNumber).toBe('PERFECT');
    const topReasons = topRec!.matchReasons as Record<string, number>;
    expect(topReasons.length_fit).toBe(100); // perfect fit
    expect(topReasons.beam_fit).toBe(100); // perfect fit
    expect(topReasons.draft_fit).toBe(100); // perfect fit

    // The second should be the too-small berth with 0 scores
    const bottomRec = recommendations[1];
    expect(bottomRec!.mooringNumber).toBe('SMALL');
    const bottomReasons = bottomRec!.matchReasons as Record<string, number>;
    expect(bottomReasons.length_fit).toBe(0); // too small
    expect(bottomReasons.beam_fit).toBe(0); // too small
    expect(bottomReasons.draft_fit).toBe(0); // too small
  });
});
