import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  extractExplicitFromText,
  detectProblemTicket,
  TICKET_NUMBER_REGEX,
  MAX_EXPLICIT_LINKS,
  discoverExplicitLinks,
} from './link-discovery';
import type { RawTicketBundle } from './preprocessor';

vi.mock('@/lib/services/postgres-client', () => ({
  default: {
    query: vi.fn(),
  },
}));

import postgresClient from '@/lib/services/postgres-client';

const mockedQuery = postgresClient.query as unknown as ReturnType<typeof vi.fn>;

function bundle(partial: Partial<RawTicketBundle['ticket']> = {}): RawTicketBundle {
  return {
    ticket: {
      id: 1,
      ticket_number: 'T20260430.0084',
      title: 'Master problem ticket — Hynes',
      description: null,
      status: 1,
      status_label: 'New',
      priority: 1,
      priority_label: 'High',
      queue_id: null,
      queue_label: null,
      company_id: 100,
      company_name: 'Hynes Industries',
      contact_id: null,
      contact_name: null,
      contact_email: null,
      assigned_resource_id: null,
      assignee_name: null,
      assignee_email: null,
      create_date: '2026-04-30T12:00:00Z',
      last_activity_date: '2026-04-30T12:00:00Z',
      resolved_date_time: null,
      problem_ticket_id: null,
      ...partial,
    },
    notes: [],
    time_entries: [],
  };
}

beforeEach(() => {
  mockedQuery.mockReset();
});

describe('TICKET_NUMBER_REGEX', () => {
  it('matches the canonical Pulse format', () => {
    const m = 'see T20260430.0084 and T20260427.0142'.match(TICKET_NUMBER_REGEX);
    expect(m).toEqual(['T20260430.0084', 'T20260427.0142']);
  });

  it('does not match invalid lengths', () => {
    expect('T2026.0084'.match(TICKET_NUMBER_REGEX)).toBeNull();
    expect('T20260430.84'.match(TICKET_NUMBER_REGEX)).toBeNull();
  });
});

describe('extractExplicitFromText', () => {
  it('returns medium-confidence refs from a free-text mention', () => {
    const r = extractExplicitFromText(
      'See T20260427.0142 for context.',
      'note_mention'
    );
    expect(r.refs).toEqual([
      { ticket_number: 'T20260427.0142', source: 'note_mention', confidence: 'medium' },
    ]);
    expect(r.hasRelatedTicketsSection).toBe(false);
  });

  it('marks refs in a RELATED TICKETS: block as high confidence', () => {
    const text = `Master problem ticket.

RELATED TICKETS:
T20260428.0053 — Allison Leone packet loss (OPEN)
T20260427.0142 — George Droder Zoom dropping (OPEN)

AFFECTED USERS:
Allison, George`;
    const r = extractExplicitFromText(text, 'description_mention');
    expect(r.hasRelatedTicketsSection).toBe(true);
    expect(r.refs).toEqual([
      {
        ticket_number: 'T20260428.0053',
        source: 'related_tickets_section',
        confidence: 'high',
      },
      {
        ticket_number: 'T20260427.0142',
        source: 'related_tickets_section',
        confidence: 'high',
      },
    ]);
  });

  it('does not mark refs after the RELATED TICKETS section ends as high', () => {
    const text = `RELATED TICKETS:
T20260428.0053 — first

OTHER NOTES:
Background investigation found T20260101.0001 was a duplicate.`;
    const r = extractExplicitFromText(text, 'description_mention');
    const high = r.refs.find((x) => x.ticket_number === 'T20260428.0053');
    const other = r.refs.find((x) => x.ticket_number === 'T20260101.0001');
    expect(high?.confidence).toBe('high');
    expect(other?.confidence).toBe('medium');
    expect(other?.source).toBe('description_mention');
  });

  it('dedupes within a single text', () => {
    const r = extractExplicitFromText(
      'T20260427.0142 first, T20260427.0142 again, and T20260427.0142 once more.',
      'note_mention'
    );
    expect(r.refs).toHaveLength(1);
  });

  it('returns empty for empty input', () => {
    expect(extractExplicitFromText('', 'note_mention').refs).toEqual([]);
  });
});

describe('detectProblemTicket', () => {
  it('flags master-problem-ticket title', () => {
    const r = detectProblemTicket(
      bundle({ title: 'Master problem ticket — recurring degradation' }),
      false
    );
    expect(r.isProblemTicket).toBe(true);
    expect(r.signals).toContain('title:master_problem_ticket');
  });

  it('flags problem-ticket title', () => {
    const r = detectProblemTicket(
      bundle({ title: 'Problem ticket: keyboard outage' }),
      false
    );
    expect(r.isProblemTicket).toBe(true);
    expect(r.signals).toContain('title:problem_ticket');
  });

  it('flags presence of RELATED TICKETS section', () => {
    const r = detectProblemTicket(
      bundle({ title: 'Plain ticket' }),
      true
    );
    expect(r.isProblemTicket).toBe(true);
    expect(r.signals).toContain('description:related_tickets_section');
  });

  it('flags problem_ticket_id column', () => {
    const r = detectProblemTicket(
      bundle({ title: 'Plain ticket', problem_ticket_id: 999 }),
      false
    );
    expect(r.isProblemTicket).toBe(true);
    expect(r.signals).toContain('column:problem_ticket_id');
  });

  it('returns false when none of the signals are present', () => {
    const r = detectProblemTicket(bundle({ title: 'Plain ticket' }), false);
    expect(r.isProblemTicket).toBe(false);
    expect(r.signals).toEqual([]);
  });
});

describe('discoverExplicitLinks', () => {
  it('skips self-references and unknown tickets', async () => {
    const b = bundle({
      description:
        'master ref T20260430.0084 (self), real ref T20260428.0053, ghost T20260101.9999',
    });
    // First call: meta lookup. Only T20260428.0053 exists.
    mockedQuery.mockResolvedValueOnce({
      rowCount: 1,
      rows: [
        {
          ticket_number: 'T20260428.0053',
          title: 'Allison Leone',
          status_label: 'Open',
          last_activity_date: '2026-04-30T10:00:00Z',
        },
      ],
    });
    const r = await discoverExplicitLinks(b);
    expect(r.explicit).toHaveLength(1);
    expect(r.explicit[0].ticket_number).toBe('T20260428.0053');
  });

  it('caps explicit refs at MAX_EXPLICIT_LINKS', async () => {
    const refs = Array.from({ length: 30 }, (_, i) => `T2026010${i}.0001`).join(', ');
    const b = bundle({ description: `Many refs: ${refs}` });
    // Return meta for all 15 it queries.
    mockedQuery.mockImplementationOnce(async (_sql: string, params: unknown[]) => {
      const numbers = params[0] as string[];
      expect(numbers.length).toBeLessThanOrEqual(MAX_EXPLICIT_LINKS);
      return {
        rowCount: numbers.length,
        rows: numbers.map((n) => ({
          ticket_number: n,
          title: 't',
          status_label: 'Open',
          last_activity_date: '2026-04-30T10:00:00Z',
        })),
      };
    });
    const r = await discoverExplicitLinks(b);
    expect(r.explicit.length).toBeLessThanOrEqual(MAX_EXPLICIT_LINKS);
  });

  it('resolves problem_ticket_id and dedupes against text mention', async () => {
    const b = bundle({
      description: 'See T20260427.0142 for context',
      problem_ticket_id: 555,
    });
    // Call 1: resolve problem_ticket_id → ticket_number.
    mockedQuery.mockResolvedValueOnce({
      rowCount: 1,
      rows: [{ ticket_number: 'T20260427.0142' }],
    });
    // Call 2: meta lookup.
    mockedQuery.mockResolvedValueOnce({
      rowCount: 1,
      rows: [
        {
          ticket_number: 'T20260427.0142',
          title: 'George Droder',
          status_label: 'Open',
          last_activity_date: '2026-04-29T10:00:00Z',
        },
      ],
    });
    const r = await discoverExplicitLinks(b);
    // Same ticket from two sources should appear once at the higher confidence.
    expect(r.explicit).toHaveLength(1);
    expect(r.explicit[0].confidence).toBe('high');
    expect(r.explicit[0].source).toBe('problem_ticket_id');
  });

  it('sorts high confidence first, then by activity date desc', async () => {
    const b = bundle({
      description: `Master.

RELATED TICKETS:
T20260428.0053 — high

Body mention: T20260427.0142 — medium`,
    });
    mockedQuery.mockResolvedValueOnce({
      rowCount: 2,
      rows: [
        {
          ticket_number: 'T20260427.0142',
          title: 'a',
          status_label: 'Open',
          last_activity_date: '2026-04-30T10:00:00Z',
        },
        {
          ticket_number: 'T20260428.0053',
          title: 'b',
          status_label: 'Open',
          last_activity_date: '2026-04-29T10:00:00Z',
        },
      ],
    });
    const r = await discoverExplicitLinks(b);
    expect(r.explicit.map((x) => x.ticket_number)).toEqual([
      'T20260428.0053',
      'T20260427.0142',
    ]);
    expect(r.isProblemTicket).toBe(true);
    expect(r.problemTicketSignals).toContain('description:related_tickets_section');
  });

  it('returns empty when there are no refs and no signals', async () => {
    const b = bundle({ description: 'No ticket refs in here.', title: 'Plain ticket' });
    mockedQuery.mockResolvedValueOnce({ rowCount: 0, rows: [] });
    const r = await discoverExplicitLinks(b);
    expect(r.explicit).toEqual([]);
    expect(r.isProblemTicket).toBe(false);
  });

  it('parses refs out of retained notes too, ignoring workflow noise', async () => {
    const b = bundle({
      description: null,
      title: 'Plain',
    });
    // Inject a workflow-noise note (filtered) and a real note (kept).
    b.notes = [
      {
        id: 1,
        title: 'Workflow Rule "Foo" fired.',
        description: 'Mentions T20260101.0001 but should be ignored',
        note_type: 13,
        publish: 1,
        creator_resource_id: 4,
        creator_name: 'Autotask Administrator',
        creator_email: null,
        creator_type: 1,
        create_date_time: '2026-04-30T12:00:00Z',
      },
      {
        id: 2,
        title: 'Tech note',
        description: 'See T20260427.0142 for the related issue',
        note_type: 1,
        publish: 1,
        creator_resource_id: 50,
        creator_name: 'Tech',
        creator_email: 'tech@wulfconsulting.com',
        creator_type: 1,
        create_date_time: '2026-04-30T13:00:00Z',
      },
    ];
    mockedQuery.mockResolvedValueOnce({
      rowCount: 1,
      rows: [
        {
          ticket_number: 'T20260427.0142',
          title: 'real',
          status_label: 'Open',
          last_activity_date: '2026-04-30T10:00:00Z',
        },
      ],
    });
    const r = await discoverExplicitLinks(b);
    expect(r.explicit.map((x) => x.ticket_number)).toEqual(['T20260427.0142']);
    expect(r.explicit[0].source).toBe('note_mention');
  });
});
