import { describe, it, expect } from 'vitest';
import { resourceToYaml, resourcePath, parseResourcePath, APPLY_ORDER } from '../src/services/backup/yaml-serializer.js';

describe('resourceToYaml', () => {
  it('serializes a server', () => {
    const yaml = resourceToYaml('server', {
      id: 'srv-1',
      name: 'grafana',
      description: 'Grafana MCP',
      dockerImage: 'mcp/grafana:latest',
      transport: 'STDIO',
      env: [{ name: 'API_KEY', value: 'secret' }],
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
      packageName: null,
      repositoryUrl: null,
    });

    expect(yaml).toContain('kind: server');
    expect(yaml).toContain('name: grafana');
    expect(yaml).toContain('description: Grafana MCP');
    expect(yaml).toContain('dockerImage: mcp/grafana:latest');
    expect(yaml).toContain('transport: STDIO');
    expect(yaml).not.toContain('id:');
    expect(yaml).not.toContain('createdAt:');
    expect(yaml).not.toContain('version:');
    expect(yaml).not.toContain('packageName:'); // null values stripped
  });

  it('serializes a project with server names', () => {
    const yaml = resourceToYaml('project', {
      id: 'p-1',
      name: 'my-project',
      description: 'Test project',
      proxyModel: 'default',
      gated: true,
      ownerId: 'user-1',
      servers: [
        { id: 'ps-1', server: { name: 'grafana' } },
        { id: 'ps-2', server: { name: 'node-red' } },
      ],
      llmProvider: 'openai',
      llmModel: null,
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('kind: project');
    expect(yaml).toContain('name: my-project');
    expect(yaml).toContain('proxyModel: default');
    expect(yaml).toContain('- grafana');
    expect(yaml).toContain('- node-red');
    expect(yaml).toContain('llmProvider: openai');
    expect(yaml).not.toContain('gated:');
    expect(yaml).not.toContain('ownerId:');
    expect(yaml).not.toContain('llmModel:'); // null stripped
  });

  it('normalizes proxyModel from gated boolean', () => {
    const yaml1 = resourceToYaml('project', {
      name: 'p1',
      proxyModel: '',
      gated: false,
      servers: [],
    });
    expect(yaml1).toContain('proxyModel: content-pipeline');

    const yaml2 = resourceToYaml('project', {
      name: 'p2',
      proxyModel: '',
      gated: true,
      servers: [],
    });
    expect(yaml2).toContain('proxyModel: default');
  });

  it('serializes a secret', () => {
    const yaml = resourceToYaml('secret', {
      id: 's-1',
      name: 'my-secret',
      data: { TOKEN: 'abc123', KEY: 'xyz' },
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('kind: secret');
    expect(yaml).toContain('name: my-secret');
    expect(yaml).toContain('TOKEN: abc123');
    expect(yaml).toContain('KEY: xyz');
  });

  it('serializes a user without passwordHash', () => {
    const yaml = resourceToYaml('user', {
      id: 'u-1',
      email: 'michal@test.com',
      name: 'Michal',
      role: 'ADMIN',
      passwordHash: '$2b$10$secret',
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('kind: user');
    expect(yaml).toContain('email: michal@test.com');
    expect(yaml).toContain('name: Michal');
    expect(yaml).toContain('role: ADMIN');
    expect(yaml).not.toContain('passwordHash');
  });

  it('serializes a group with member emails', () => {
    const yaml = resourceToYaml('group', {
      id: 'g-1',
      name: 'dev-team',
      description: 'Developers',
      members: [
        { user: { email: 'alice@test.com' } },
        { user: { email: 'bob@test.com' } },
      ],
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('kind: group');
    expect(yaml).toContain('name: dev-team');
    expect(yaml).toContain('- alice@test.com');
    expect(yaml).toContain('- bob@test.com');
  });

  it('serializes a prompt with project name', () => {
    const yaml = resourceToYaml('prompt', {
      id: 'pr-1',
      name: 'system-instructions',
      content: 'You are a helpful assistant.',
      priority: 5,
      project: { name: 'my-project' },
      projectId: 'p-1',
      summary: 'Summary text',
      chapters: ['ch1'],
      linkTarget: null,
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('kind: prompt');
    expect(yaml).toContain('name: system-instructions');
    expect(yaml).toContain('project: my-project');
    expect(yaml).toContain('priority: 5');
    expect(yaml).toContain('content: You are a helpful assistant.');
    expect(yaml).not.toContain('projectId:');
    expect(yaml).not.toContain('summary:');
    expect(yaml).not.toContain('chapters:');
  });

  it('serializes a linked prompt with link field', () => {
    const yaml = resourceToYaml('prompt', {
      id: 'pr-2',
      name: 'linked-prompt',
      content: 'Fetched content',
      linkTarget: 'my-project/grafana:resource://docs',
      project: { name: 'my-project' },
      projectId: 'p-1',
      priority: 3,
      version: 1,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    expect(yaml).toContain('link: my-project/grafana:resource://docs');
    expect(yaml).not.toContain('content:'); // content stripped for linked prompts
    expect(yaml).not.toContain('linkTarget:');
  });

  it('puts kind first and content/data last', () => {
    const yaml = resourceToYaml('secret', {
      name: 'test',
      data: { KEY: 'val' },
    });
    const lines = yaml.split('\n');
    expect(lines[0]).toBe('kind: secret');
    // data should be after name
    const nameIdx = lines.findIndex((l) => l.startsWith('name:'));
    const dataIdx = lines.findIndex((l) => l.startsWith('data:'));
    expect(dataIdx).toBeGreaterThan(nameIdx);
  });
});

describe('resourcePath', () => {
  it('maps kinds to directories', () => {
    expect(resourcePath('server', 'grafana')).toBe('servers/grafana.yaml');
    expect(resourcePath('secret', 'my-token')).toBe('secrets/my-token.yaml');
    expect(resourcePath('project', 'default')).toBe('projects/default.yaml');
    expect(resourcePath('rbac', 'admins')).toBe('rbac/admins.yaml');
    expect(resourcePath('user', 'michal@test.com')).toBe('users/michal@test.com.yaml');
  });

  it('sanitizes unsafe characters', () => {
    expect(resourcePath('server', 'my/server')).toBe('servers/my_server.yaml');
  });
});

describe('parseResourcePath', () => {
  it('parses valid paths', () => {
    expect(parseResourcePath('servers/grafana.yaml')).toEqual({ kind: 'server', name: 'grafana' });
    expect(parseResourcePath('secrets/my-token.yaml')).toEqual({ kind: 'secret', name: 'my-token' });
    expect(parseResourcePath('rbac/admins.yaml')).toEqual({ kind: 'rbac', name: 'admins' });
  });

  it('returns null for invalid paths', () => {
    expect(parseResourcePath('README.md')).toBeNull();
    expect(parseResourcePath('.gitkeep')).toBeNull();
    expect(parseResourcePath('unknown/file.yaml')).toBeNull();
  });
});

describe('APPLY_ORDER', () => {
  it('has secrets before servers before projects', () => {
    const si = APPLY_ORDER.indexOf('secret');
    const sv = APPLY_ORDER.indexOf('server');
    const pr = APPLY_ORDER.indexOf('project');
    expect(si).toBeLessThan(sv);
    expect(sv).toBeLessThan(pr);
  });

  it('has all backup kinds', () => {
    // PR-3: bumped from 8 → 9 with the addition of `skill`.
    expect(APPLY_ORDER).toHaveLength(9);
  });
});
