/**
 * Integration tests for server.js input validation
 * Tests that routes properly reject invalid input before reaching business logic
 */

const request = require('supertest');
const app = require('../server');

describe('POST /api/assets/upload - directory traversal prevention', () => {
  test('rejects filename with path separators', async () => {
    const res = await request(app)
      .post('/api/assets/upload')
      .send({ filename: '../../../etc/passwd', data: 'data:image/png;base64,iVBOR' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/path separator/i);
  });

  test('rejects filename with backslash', async () => {
    const res = await request(app)
      .post('/api/assets/upload')
      .send({ filename: '..\\..\\config.json', data: 'data:image/png;base64,iVBOR' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/path separator/i);
  });

  test('rejects filename with dot-dot', async () => {
    const res = await request(app)
      .post('/api/assets/upload')
      .send({ filename: '..evil.png', data: 'data:image/png;base64,iVBOR' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/path separator/i);
  });

  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/assets/upload')
      .send({ filename: 'test.png' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });
});

describe('POST /api/site - Caddyfile injection prevention', () => {
  test('rejects invalid domain format', async () => {
    const res = await request(app)
      .post('/api/site')
      .send({ domain: 'evil;rm -rf /', upstream: '127.0.0.1:8080' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid domain/i);
  });

  test('rejects domain with spaces', async () => {
    const res = await request(app)
      .post('/api/site')
      .send({ domain: 'evil domain', upstream: '127.0.0.1:8080' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid domain/i);
  });

  test('rejects invalid upstream format', async () => {
    const res = await request(app)
      .post('/api/site')
      .send({ domain: 'test.sami', upstream: 'not a valid upstream' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid upstream/i);
  });

  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/site')
      .send({ domain: 'test.sami' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });
});

describe('POST /api/site/external - URL and subdomain validation', () => {
  test('rejects invalid subdomain', async () => {
    const res = await request(app)
      .post('/api/site/external')
      .send({ subdomain: '-invalid', externalUrl: 'https://example.com' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid subdomain/i);
  });

  test('rejects subdomain with special chars', async () => {
    const res = await request(app)
      .post('/api/site/external')
      .send({ subdomain: 'test;evil', externalUrl: 'https://example.com' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid subdomain/i);
  });

  test('rejects invalid URL', async () => {
    const res = await request(app)
      .post('/api/site/external')
      .send({ subdomain: 'myapp', externalUrl: 'not-a-url' });
    expect(res.status).toBe(400);
    expect(res.body.success).toBe(false);
  });

  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/site/external')
      .send({ subdomain: 'myapp' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });
});

// DNS routes require a token to bypass the 401 token check and reach validation
const FAKE_TOKEN = 'aaaa1111bbbb2222cccc3333dddd4444';

describe('POST /api/dns/record - DNS injection prevention', () => {
  test('rejects invalid domain format', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'evil;command', ip: '10.0.0.1', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid domain/i);
  });

  test('rejects invalid IP address', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'test.sami', ip: 'not-an-ip', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid ip/i);
  });

  test('rejects TTL out of range (too low)', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'test.sami', ip: '10.0.0.1', ttl: 5, token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/ttl/i);
  });

  test('rejects TTL out of range (too high)', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'test.sami', ip: '10.0.0.1', ttl: 100000, token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/ttl/i);
  });

  test('rejects invalid server IP', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'test.sami', ip: '10.0.0.1', server: 'not-an-ip', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid dns server/i);
  });

  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/dns/record')
      .send({ domain: 'test.sami', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
  });
});

describe('DELETE /api/dns/record - DNS injection prevention', () => {
  test('rejects invalid domain', async () => {
    const res = await request(app)
      .delete('/api/dns/record')
      .query({ domain: 'evil;drop table', token: 'abc123def456' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid domain/i);
  });

  test('rejects invalid record type', async () => {
    const res = await request(app)
      .delete('/api/dns/record')
      .query({ domain: 'test.sami', type: 'INVALID', token: 'abc123def456' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid dns record type/i);
  });

  test('rejects invalid ipAddress', async () => {
    const res = await request(app)
      .delete('/api/dns/record')
      .query({ domain: 'test.sami', ipAddress: 'not-ip', token: 'abc123def456' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid ip/i);
  });
});

describe('GET /api/dns/resolve - DNS injection prevention', () => {
  test('rejects invalid domain', async () => {
    const res = await request(app)
      .get('/api/dns/resolve')
      .query({ domain: 'evil;command', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid domain/i);
  });

  test('rejects invalid server IP', async () => {
    const res = await request(app)
      .get('/api/dns/resolve')
      .query({ domain: 'test.sami', server: 'not-an-ip', token: FAKE_TOKEN });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid dns server/i);
  });
});

describe('POST /api/apps/deploy - deployment validation', () => {
  test('rejects invalid subdomain', async () => {
    const res = await request(app)
      .post('/api/apps/deploy')
      .send({ appId: 'plex', config: { subdomain: '-bad-sub' } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid subdomain/i);
  });

  test('rejects invalid port', async () => {
    const res = await request(app)
      .post('/api/apps/deploy')
      .send({ appId: 'plex', config: { subdomain: 'test', port: 99999 } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid port/i);
  });

  test('rejects invalid IP', async () => {
    const res = await request(app)
      .post('/api/apps/deploy')
      .send({ appId: 'plex', config: { subdomain: 'test', ip: 'not-ip' } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid ip/i);
  });

  test('rejects unknown template', async () => {
    const res = await request(app)
      .post('/api/apps/deploy')
      .send({ appId: 'nonexistent-app-xyz', config: { subdomain: 'test' } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid app template/i);
  });
});

describe('POST /api/dns/credentials - credential validation', () => {
  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/dns/credentials')
      .send({ username: 'admin' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });

  test('rejects username exceeding max length', async () => {
    const res = await request(app)
      .post('/api/dns/credentials')
      .send({ username: 'a'.repeat(101), password: 'secret' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/maximum length/i);
  });

  test('rejects username with injection chars', async () => {
    const res = await request(app)
      .post('/api/dns/credentials')
      .send({ username: 'admin;rm -rf /', password: 'secret' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid characters/i);
  });

  test('rejects username with pipe', async () => {
    const res = await request(app)
      .post('/api/dns/credentials')
      .send({ username: 'admin|evil', password: 'secret' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid characters/i);
  });

  test('rejects invalid server IP', async () => {
    const res = await request(app)
      .post('/api/dns/credentials')
      .send({ username: 'admin', password: 'secret', server: 'not-ip' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid dns server/i);
  });
});

describe('POST /api/services - service config validation', () => {
  test('rejects missing fields', async () => {
    const res = await request(app)
      .post('/api/services')
      .send({ id: 'test' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });

  test('rejects invalid service id format', async () => {
    const res = await request(app)
      .post('/api/services')
      .send({ id: 'invalid id with spaces!', name: 'Test' });
    expect(res.status).toBe(400);
    expect(res.body.success).toBe(false);
  });
});

describe('PUT /api/services - bulk import validation', () => {
  test('rejects non-array body', async () => {
    const res = await request(app)
      .put('/api/services')
      .send({ id: 'test', name: 'Test' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/array/i);
  });

  test('rejects service with invalid id', async () => {
    const res = await request(app)
      .put('/api/services')
      .send([{ id: 'invalid id!', name: 'Test' }]);
    expect(res.status).toBe(400);
    expect(res.body.success).toBe(false);
  });
});

describe('POST /api/services/update - service update validation', () => {
  test('rejects missing subdomains', async () => {
    const res = await request(app)
      .post('/api/services/update')
      .send({ oldSubdomain: 'test' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/required/i);
  });

  test('rejects invalid subdomain format', async () => {
    const res = await request(app)
      .post('/api/services/update')
      .send({ oldSubdomain: '-bad', newSubdomain: 'good' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid subdomain/i);
  });

  test('rejects invalid port', async () => {
    const res = await request(app)
      .post('/api/services/update')
      .send({ oldSubdomain: 'old', newSubdomain: 'new', port: 70000 });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid port/i);
  });

  test('rejects invalid IP', async () => {
    const res = await request(app)
      .post('/api/services/update')
      .send({ oldSubdomain: 'old', newSubdomain: 'new', ip: 'not-ip' });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/invalid ip/i);
  });
});

describe('POST /api/arr/test-connection - SSRF prevention', () => {
  test('rejects invalid URL', async () => {
    const res = await request(app)
      .post('/api/arr/test-connection')
      .send({ service: 'radarr', url: 'not-a-url', apiKey: 'abc123def456' });
    expect(res.status).toBe(400);
    expect(res.body.success).toBe(false);
  });

  test('rejects invalid API key format', async () => {
    const res = await request(app)
      .post('/api/arr/test-connection')
      .send({ service: 'radarr', url: 'http://localhost:7878', apiKey: 'a;b' });
    expect(res.status).toBe(400);
    expect(res.body.success).toBe(false);
  });
});

describe('POST /api/notifications/config - notification provider validation', () => {
  test('rejects invalid Discord webhook URL', async () => {
    const res = await request(app)
      .post('/api/notifications/config')
      .send({ providers: { discord: { webhookUrl: 'not-a-url' } } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/discord webhook/i);
  });

  test('rejects invalid ntfy server URL', async () => {
    const res = await request(app)
      .post('/api/notifications/config')
      .send({ providers: { ntfy: { serverUrl: 'ftp://bad' } } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/ntfy server/i);
  });

  test('rejects invalid ntfy topic', async () => {
    const res = await request(app)
      .post('/api/notifications/config')
      .send({ providers: { ntfy: { topic: 'has spaces and $pecial!' } } });
    expect(res.status).toBe(400);
    expect(res.body.error).toMatch(/ntfy topic/i);
  });

  test('accepts valid config', async () => {
    const res = await request(app)
      .post('/api/notifications/config')
      .send({ enabled: true });
    expect(res.status).toBe(200);
    expect(res.body.success).toBe(true);
  });
});

describe('Rate limiting headers', () => {
  test('returns rate limit headers on API responses', async () => {
    const res = await request(app).get('/api/health');
    // Health endpoint is skipped by rate limiter, but general endpoints should have headers
    expect(res.status).toBe(200);
  });

  test('general API endpoint has rate limiting configured', async () => {
    const res = await request(app).get('/api/services');
    // Rate limiting is skipped in test env, so verify the endpoint is accessible
    expect(res.status).toBe(200);
  });
});
