import { describe, expect, it, vi } from 'vitest';
import {
  buildContactCreatePayload,
  buildContactListQuery,
  buildContactUpdatePayload,
  buildProjectTaskCreatePayload,
  buildProjectTaskListQuery,
  buildProjectTaskUpdatePayload,
  formatAlgaApiError,
  normalizeSuccessResponse,
  parseContactEmailAddresses,
  parseContactPhoneNumbers,
} from '../nodes/AlgaPsa/helpers';
import { buildAlgaApiRequestOptions } from '../nodes/AlgaPsa/transport';

describe('Transport and normalization helpers', () => {
  it('T004: request helper normalizes base URL and endpoint slashes', () => {
    const options = buildAlgaApiRequestOptions(
      {
        baseUrl: 'https://api.algapsa.test///',
        apiKey: 'secret-key',
      },
      'GET',
      'api/v1/tickets',
      { page: 1 },
    );

    expect(options.url).toBe('https://api.algapsa.test/api/v1/tickets');
    expect(options.qs).toEqual({ page: 1 });
  });

  it('T005: request helper injects x-api-key and does not log credential values', () => {
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);

    const options = buildAlgaApiRequestOptions(
      {
        baseUrl: 'https://api.algapsa.test',
        apiKey: 'sensitive-key',
      },
      'POST',
      '/api/v1/tickets',
      undefined,
      { title: 'Example' },
    );

    expect(options.headers?.['x-api-key']).toBe('sensitive-key');
    expect(options.url).not.toContain('sensitive-key');
    expect(logSpy).not.toHaveBeenCalled();
    expect(warnSpy).not.toHaveBeenCalled();
    expect(errorSpy).not.toHaveBeenCalled();

    logSpy.mockRestore();
    warnSpy.mockRestore();
    errorSpy.mockRestore();
  });

  it('T025: response normalizer unwraps single-object data responses', () => {
    const normalized = normalizeSuccessResponse({ data: { ticket_id: 'abc', title: 'A' } });
    expect(normalized).toEqual({ ticket_id: 'abc', title: 'A' });
  });

  it('T026: response normalizer unwraps list data and preserves pagination metadata', () => {
    const normalized = normalizeSuccessResponse({
      data: [{ ticket_id: 'abc' }],
      pagination: { page: 1, total: 1 },
    });

    expect(normalized).toEqual({
      data: [{ ticket_id: 'abc' }],
      pagination: { page: 1, total: 1 },
    });
  });

  it('T027: maps 401 API response into actionable error shape', () => {
    const parsed = formatAlgaApiError({
      response: {
        status: 401,
        data: {
          error: {
            code: 'UNAUTHORIZED',
            message: 'Invalid API key',
            details: { reason: 'missing key' },
          },
        },
      },
    });

    expect(parsed).toEqual({
      statusCode: 401,
      code: 'UNAUTHORIZED',
      message: 'Invalid API key',
      details: { reason: 'missing key' },
    });
  });

  it('T028: maps 403 API response into actionable error shape', () => {
    const parsed = formatAlgaApiError({
      response: {
        status: 403,
        data: {
          error: {
            code: 'FORBIDDEN',
            message: 'Permission denied',
            details: { permission: 'ticket:update' },
          },
        },
      },
    });

    expect(parsed.code).toBe('FORBIDDEN');
    expect(parsed.statusCode).toBe(403);
    expect(parsed.details).toEqual({ permission: 'ticket:update' });
  });

  it('T029: maps 404 API response into actionable error shape', () => {
    const parsed = formatAlgaApiError({
      response: {
        status: 404,
        data: {
          error: {
            code: 'NOT_FOUND',
            message: 'Ticket not found',
            details: { ticketId: 'missing-id' },
          },
        },
      },
    });

    expect(parsed.code).toBe('NOT_FOUND');
    expect(parsed.message).toBe('Ticket not found');
    expect(parsed.statusCode).toBe(404);
  });

  it('T030: maps 400 API response into actionable error shape', () => {
    const parsed = formatAlgaApiError({
      response: {
        status: 400,
        data: {
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Validation failed',
            details: [{ path: ['status_id'], message: 'Invalid UUID' }],
          },
        },
      },
    });

    expect(parsed.code).toBe('VALIDATION_ERROR');
    expect(parsed.message).toBe('Validation failed');
    expect(parsed.statusCode).toBe(400);
    expect(parsed.details).toEqual([{ path: ['status_id'], message: 'Invalid UUID' }]);
  });

  it('T011: contact create payload builder maps full_name and omits absent optional fields', () => {
    const payload = buildContactCreatePayload({
      fullName: 'Ada Lovelace',
      additionalFields: {},
    });

    expect(payload).toEqual({ full_name: 'Ada Lovelace' });
  });

  it('T012: contact create payload builder includes scalar optional fields when present', () => {
    const payload = buildContactCreatePayload({
      fullName: 'Ada Lovelace',
      additionalFields: {
        email: 'ada@example.com',
        client_id: '00000000-0000-0000-0000-000000000001',
        role: 'CTO',
        notes: 'Primary automation contact',
        is_inactive: true,
      },
    });

    expect(payload).toEqual({
      full_name: 'Ada Lovelace',
      email: 'ada@example.com',
      client_id: '00000000-0000-0000-0000-000000000001',
      role: 'CTO',
      notes: 'Primary automation contact',
      is_inactive: true,
    });
  });

  it('T013: contact update payload builder includes only provided update fields', () => {
    const payload = buildContactUpdatePayload({
      full_name: 'Updated Contact',
      email: '',
      client_id: '00000000-0000-0000-0000-000000000002',
      notes: 'Updated via n8n',
    });

    expect(payload).toEqual({
      full_name: 'Updated Contact',
      client_id: '00000000-0000-0000-0000-000000000002',
      notes: 'Updated via n8n',
    });
  });

  it('contact payload builders accept primary email metadata and additional email rows', () => {
    const createPayload = buildContactCreatePayload({
      fullName: 'Ada Lovelace',
      additionalFields: {
        email: 'ada@example.com',
        primary_email_canonical_type: 'billing',
        additional_email_addresses: JSON.stringify([
          {
            email_address: 'ada.personal@example.com',
            canonical_type: 'personal',
            display_order: 0,
          },
        ]),
      },
    });

    expect(createPayload).toEqual({
      full_name: 'Ada Lovelace',
      email: 'ada@example.com',
      primary_email_canonical_type: 'billing',
      additional_email_addresses: [
        {
          email_address: 'ada.personal@example.com',
          canonical_type: 'personal',
          display_order: 0,
        },
      ],
    });

    const updatePayload = buildContactUpdatePayload({
      primary_email_custom_type: 'Escalations',
      additional_email_addresses: JSON.stringify([
        {
          contact_additional_email_address_id: '00000000-0000-0000-0000-000000000010',
          email_address: 'ada.billing@example.com',
          custom_type: 'Billing Alias',
          display_order: 1,
        },
      ]),
    });

    expect(updatePayload).toEqual({
      primary_email_custom_type: 'Escalations',
      additional_email_addresses: [
        {
          contact_additional_email_address_id: '00000000-0000-0000-0000-000000000010',
          email_address: 'ada.billing@example.com',
          custom_type: 'Billing Alias',
          display_order: 1,
        },
      ],
    });
  });

  it('T014: contact list query builder serializes pagination and core filters correctly', () => {
    const query = buildContactListQuery({
      page: 3,
      limit: 50,
      filters: {
        client_id: '00000000-0000-0000-0000-000000000003',
        search_term: 'ada',
        is_inactive: false,
      },
    });

    expect(query).toEqual({
      page: 3,
      limit: 50,
      client_id: '00000000-0000-0000-0000-000000000003',
      search_term: 'ada',
      is_inactive: false,
    });
  });

  it('T015: phone_numbers parser accepts a valid JSON array of contact phone-number objects', () => {
    const parsed = parseContactPhoneNumbers(
      JSON.stringify([
        {
          phone_number: '+1-206-555-0100',
          canonical_type: 'mobile',
          is_default: true,
          display_order: 0,
        },
      ]),
    );

    expect(parsed).toEqual([
      {
        phone_number: '+1-206-555-0100',
        canonical_type: 'mobile',
        is_default: true,
        display_order: 0,
      },
    ]);
  });

  it('T016: phone_numbers parser rejects malformed JSON before any request is sent', () => {
    expect(() => parseContactPhoneNumbers('[{')).toThrow('phone_numbers must be valid JSON');
  });

  it('T017: phone_numbers parser rejects non-array JSON values before any request is sent', () => {
    expect(() => parseContactPhoneNumbers('{"phone_number":"+1-206-555-0100"}')).toThrow(
      'phone_numbers must be a JSON array',
    );
  });

  it('T018: phone_numbers parser rejects array entries that are missing phone_number', () => {
    expect(() =>
      parseContactPhoneNumbers(
        JSON.stringify([
          {
            canonical_type: 'mobile',
          },
        ]),
      ),
    ).toThrow('phone_numbers[0].phone_number is required');
  });

  it('parseContactEmailAddresses accepts JSON arrays of labeled additional email rows', () => {
    const parsed = parseContactEmailAddresses(
      JSON.stringify([
        {
          email_address: 'ada.personal@example.com',
          canonical_type: 'personal',
          display_order: 0,
        },
      ]),
    );

    expect(parsed).toEqual([
      {
        email_address: 'ada.personal@example.com',
        canonical_type: 'personal',
        display_order: 0,
      },
    ]);
  });

  it('T070: project task create payload maps required fields and omits absent optional fields', () => {
    const payload = buildProjectTaskCreatePayload({
      taskName: 'Write specification',
      statusMappingId: '00000000-0000-0000-0000-000000000301',
      additionalFields: {},
    });

    expect(payload).toEqual({
      task_name: 'Write specification',
      project_status_mapping_id: '00000000-0000-0000-0000-000000000301',
    });
  });

  it('T071: project task create payload includes scalar optional fields when present', () => {
    const payload = buildProjectTaskCreatePayload({
      taskName: 'Write specification',
      statusMappingId: '00000000-0000-0000-0000-000000000301',
      additionalFields: {
        description: 'Draft the functional spec',
        assigned_to: '00000000-0000-0000-0000-000000000302',
        estimated_hours: 4.5,
        due_date: '2026-05-01',
        priority_id: '00000000-0000-0000-0000-000000000303',
        task_type_key: 'design',
        wbs_code: '1.1',
        tags: 'backend, planning',
      },
    });

    expect(payload).toEqual({
      task_name: 'Write specification',
      project_status_mapping_id: '00000000-0000-0000-0000-000000000301',
      description: 'Draft the functional spec',
      assigned_to: '00000000-0000-0000-0000-000000000302',
      estimated_hours: 4.5,
      due_date: '2026-05-01',
      priority_id: '00000000-0000-0000-0000-000000000303',
      task_type_key: 'design',
      wbs_code: '1.1',
      tags: ['backend', 'planning'],
    });
  });

  it('T072: project task create payload rejects non-UUID assigned_to before request time', () => {
    expect(() =>
      buildProjectTaskCreatePayload({
        taskName: 'Task',
        statusMappingId: '00000000-0000-0000-0000-000000000301',
        additionalFields: { assigned_to: 'not-a-uuid' },
      }),
    ).toThrow('assigned_to must be a valid UUID');
  });

  it('T073: project task create payload rejects negative estimated_hours before request time', () => {
    expect(() =>
      buildProjectTaskCreatePayload({
        taskName: 'Task',
        statusMappingId: '00000000-0000-0000-0000-000000000301',
        additionalFields: { estimated_hours: -1 },
      }),
    ).toThrow('estimated_hours must be a non-negative number');
  });

  it('T074: project task update payload includes only provided update fields', () => {
    const payload = buildProjectTaskUpdatePayload({
      task_name: 'Renamed',
      description: '',
      project_status_mapping_id: '00000000-0000-0000-0000-000000000304',
      tags: 'escalated',
    });

    expect(payload).toEqual({
      task_name: 'Renamed',
      project_status_mapping_id: '00000000-0000-0000-0000-000000000304',
      tags: ['escalated'],
    });
  });

  it('T075: project task list query serializes pagination parameters', () => {
    expect(
      buildProjectTaskListQuery({
        page: 3,
        limit: 50,
      }),
    ).toEqual({ page: 3, limit: 50 });
  });
});
