import { describe, expect, it } from 'vitest';
import { parseDbtSchemaFile, parseDbtSchemaFiles } from '../../../../../src/context/ingest/adapters/dbt-descriptions/parse-schema.js';

describe('dbt descriptions schema parser', () => {
  it('resolves shared dbt vars and defaults before parsing schema YAML', () => {
    const result = parseDbtSchemaFile(
      `
version: 2
sources:
  - name: raw
    database: "{{ var('database') }}"
    schema: "{{ var('schema', 'fallback_schema') }}"
    tables:
      - name: orders
        identifier: fct_orders
        description: "Orders from {{ var('database') }}"
        columns:
          - name: customer_id
            description: "Customer id"
            tests:
              - relationships:
                  to: ref('customers')
                  field: id
models:
  - name: "{{ var('model_name', 'orders_model') }}"
    schema: "{{ var('model_schema') }}"
    columns:
      - name: id
        description: "Order id"
`,
      { path: 'models/schema.yml', variables: new Map([['database', 'analytics'], ['model_schema', 'mart']]) },
    );

    expect(result.tables).toEqual([
      {
        name: 'fct_orders',
        description: 'Orders from analytics',
        database: 'analytics',
        schema: 'fallback_schema',
        columns: [
          {
            name: 'customer_id',
            description: 'Customer id',
            dataType: null,
            dataTests: [{ name: 'relationships', package: 'dbt', kwargs: { to: "ref('customers')", field: 'id' } }],
          },
        ],
        resourceType: 'source',
      },
      {
        name: 'orders_model',
        description: null,
        database: null,
        schema: 'mart',
        columns: [{ name: 'id', description: 'Order id', dataType: null }],
        resourceType: 'model',
      },
    ]);
    expect(result.relationships).toEqual([
      {
        fromTable: 'fct_orders',
        fromColumn: 'customer_id',
        toTable: 'customers',
        toColumn: 'id',
        fromSchema: 'fallback_schema',
      },
    ]);
  });

  it('deduplicates tables by database schema and name while merging columns', () => {
    const result = parseDbtSchemaFiles([
      {
        path: 'models/a.yml',
        content: `
version: 2
models:
  - name: orders
    description: Orders
    columns:
      - name: id
        description: Primary key
`,
      },
      {
        path: 'models/b.yml',
        content: `
version: 2
models:
  - name: orders
    columns:
      - name: status
        description: Status
      - name: id
        data_type: integer
`,
      },
    ]);

    expect(result.tables).toEqual([
      {
        name: 'orders',
        description: 'Orders',
        database: null,
        schema: null,
        resourceType: 'model',
        columns: [
          { name: 'id', description: 'Primary key', dataType: 'integer' },
          { name: 'status', description: 'Status', dataType: null },
        ],
      },
    ]);
  });

  it('returns an empty result for malformed YAML and preserves unresolved Jinja text', () => {
    expect(parseDbtSchemaFile('{{{{ invalid yaml', { path: 'broken.yml' })).toEqual({
      projectName: null,
      dbtVersion: null,
      tables: [],
      relationships: [],
    });

    const unresolved = parseDbtSchemaFile(
      `
version: 2
models:
  - name: "{{ var('missing_model') }}"
`,
      { variables: new Map() },
    );
    expect(unresolved.tables[0]?.name).toBe("{{ var('missing_model') }}");
  });

  it('extracts data tests, constraints, enum values, tags, and freshness', () => {
    const result = parseDbtSchemaFile(`
version: 2
sources:
  - name: raw
    schema: jaffle
    tags: ["raw"]
    tables:
      - name: customers
        tags: ["core"]
        loaded_at_field: updated_at
        freshness:
          warn_after: { count: 12, period: hour }
        columns:
          - name: id
            tests:
              - not_null
              - unique
          - name: status
            data_tests:
              - accepted_values:
                  values: ['active', 'inactive']
models:
  - name: orders
    tags: ["finance"]
    loaded_at_field: run_at
    columns:
      - name: status
        data_tests:
          - dbt_utils.expression_is_true:
              expression: "status is not null"
          - accepted_values: ['placed', 'shipped']
`);

    const customers = result.tables.find((table) => table.name === 'customers');
    expect(customers?.tagsDbt).toEqual(['raw', 'core']);
    expect(customers?.freshnessDbt?.loadedAtField).toBe('updated_at');
    expect(customers?.freshnessDbt?.raw).toBeDefined();
    const id = customers?.columns.find((column) => column.name === 'id');
    expect(id?.constraints?.dbt).toEqual({ not_null: true, unique: true });
    const status = customers?.columns.find((column) => column.name === 'status');
    expect(status?.enumValuesDbt).toEqual(['active', 'inactive']);

    const orders = result.tables.find((table) => table.name === 'orders');
    expect(orders?.tagsDbt).toEqual(['finance']);
    expect(orders?.freshnessDbt?.loadedAtField).toBe('run_at');
    const ordersStatus = orders?.columns.find((column) => column.name === 'status');
    expect(ordersStatus?.enumValuesDbt).toEqual(['placed', 'shipped']);
    expect(ordersStatus?.dataTests).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ package: 'dbt_utils', name: 'expression_is_true' }),
        expect.objectContaining({ package: 'dbt', name: 'accepted_values' }),
      ]),
    );
  });

  it('parses relationships from model column data tests', () => {
    const result = parseDbtSchemaFile(`
version: 2
models:
  - name: orders
    schema: public
    columns:
      - name: customer_id
        data_tests:
          - relationships:
              arguments:
                to: "ref('customers')"
                field: id
`);

    expect(result.relationships).toEqual([
      {
        fromTable: 'orders',
        fromColumn: 'customer_id',
        toTable: 'customers',
        toColumn: 'id',
        fromSchema: 'public',
      },
    ]);
  });
});
