import test from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
import { load, makeUser } from './setup.mjs';

const {
  buildVmListPath,
  buildHostPayloadFromVm,
  buildStandaloneHostCandidate,
  createCredential,
  createVCenterConnection,
  filterSummaryVms,
  getVCenterConnection,
  getVCenterVmPowerState,
  normalizeBaseUrl,
  normalizeVCenterVm,
  previewVCenterHosts,
  settingsFromVCenterConnection,
  vmMatchesFilter
} = {
  ...(await load('../services/vcenterService.js')),
  ...(await load('../models/credentialModel.js')),
  ...(await load('../models/vcenterConnectionModel.js'))
};

const originalFetch = globalThis.fetch;

function soapResponse(body, init = {}) {
  return new Response(`<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>${body}</soapenv:Body></soapenv:Envelope>`, {
    status: 200,
    headers: { 'content-type': 'text/xml', ...(init.headers || {}) }
  });
}

function installStandaloneEsxiSoapStub({ sdk404 = false } = {}) {
  const calls = [];
  globalThis.fetch = async (url, options = {}) => {
    calls.push({ url: String(url), method: options.method || 'GET', body: String(options.body || ''), headers: options.headers || {} });
    const body = String(options.body || '');
    if (options.method === 'GET') return new Response('<definitions>vimService</definitions>', { status: 200, headers: { 'content-type': 'text/xml' } });
    if (sdk404 && String(url).endsWith('/sdk')) return new Response('not found', { status: 404, statusText: 'Not Found', headers: { 'content-type': 'text/plain' } });
    if (body.includes('<RetrieveServiceContent')) {
      return soapResponse(`<RetrieveServiceContentResponse xmlns="urn:vim25"><returnval><rootFolder type="Folder">ha-folder-root</rootFolder><propertyCollector type="PropertyCollector">ha-property-collector</propertyCollector><viewManager type="ViewManager">ViewManager</viewManager><sessionManager type="SessionManager">ha-sessionmgr</sessionManager></returnval></RetrieveServiceContentResponse>`);
    }
    if (body.includes('<Login')) {
      return soapResponse(`<LoginResponse xmlns="urn:vim25"><returnval type="UserSession">session-1</returnval></LoginResponse>`, { headers: { 'set-cookie': 'vmware_soap_session=session-1; Path=/sdk; Secure' } });
    }
    if (body.includes('<CreateContainerView')) {
      return soapResponse(`<CreateContainerViewResponse xmlns="urn:vim25"><returnval type="ContainerView">session[52b6]-vm-view</returnval></CreateContainerViewResponse>`);
    }
    if (body.includes('<RetrievePropertiesEx')) {
      return soapResponse(`<RetrievePropertiesExResponse xmlns="urn:vim25"><returnval><objects><obj type="VirtualMachine">vim.VirtualMachine:101</obj><propSet><name>name</name><val>ENG-ENT-APP01</val></propSet><propSet><name>guest.hostName</name><val>eng-ent-app01.contoso.local</val></propSet><propSet><name>guest.ipAddress</name><val>10.42.8.21</val></propSet><propSet><name>guest.guestFullName</name><val>Microsoft Windows Server 2022</val></propSet><propSet><name>runtime.powerState</name><val>poweredOn</val></propSet></objects></returnval></RetrievePropertiesExResponse>`);
    }
    return soapResponse('');
  };
  return { calls, restore: () => { globalThis.fetch = originalFetch; } };
}

function startStandaloneEsxiSoapServer() {
  const requests = [];
  const server = http.createServer((req, res) => {
    let body = '';
    req.on('data', (chunk) => { body += chunk; });
    req.on('end', () => {
      requests.push({ url: req.url, method: req.method, body, cookie: req.headers.cookie || '' });
      res.setHeader('content-type', 'text/xml');
      if (req.method === 'GET') {
        res.end('<definitions>vimService</definitions>');
      } else if (body.includes('<Login')) {
        res.setHeader('set-cookie', 'vmware_soap_session=session-http; Path=/sdk');
        res.end(`<?xml version="1.0"?><Envelope><Body><LoginResponse><returnval type="UserSession">session-http</returnval></LoginResponse></Body></Envelope>`);
      } else if (body.includes('<RetrieveServiceContent')) {
        res.end(`<?xml version="1.0"?><Envelope><Body><RetrieveServiceContentResponse><returnval><rootFolder type="Folder">ha-folder-root</rootFolder><propertyCollector type="PropertyCollector">ha-property-collector</propertyCollector><viewManager type="ViewManager">ViewManager</viewManager><sessionManager type="SessionManager">ha-sessionmgr</sessionManager></returnval></RetrieveServiceContentResponse></Body></Envelope>`);
      } else if (body.includes('<CreateContainerView')) {
        res.end(`<?xml version="1.0"?><Envelope><Body><CreateContainerViewResponse><returnval type="ContainerView">session-http-vm-view</returnval></CreateContainerViewResponse></Body></Envelope>`);
      } else if (body.includes('<RetrievePropertiesEx')) {
        res.end(`<?xml version="1.0"?><Envelope><Body><RetrievePropertiesExResponse><returnval><objects><obj type="VirtualMachine">vim.VirtualMachine:201</obj><propSet><name>name</name><val>HTTP-ESXI-VM01</val></propSet><propSet><name>guest.hostName</name><val>http-esxi-vm01.lab.local</val></propSet><propSet><name>guest.ipAddress</name><val>10.42.9.21</val></propSet><propSet><name>guest.guestFullName</name><val>Microsoft Windows Server 2022</val></propSet><propSet><name>runtime.powerState</name><val>poweredOn</val></propSet></objects></returnval></RetrievePropertiesExResponse></Body></Envelope>`);
      } else {
        res.end(`<?xml version="1.0"?><Envelope><Body /></Envelope>`);
      }
    });
  });
  return new Promise((resolve) => {
    server.listen(0, '127.0.0.1', () => {
      resolve({
        baseUrl: `http://127.0.0.1:${server.address().port}`,
        requests,
        close: () => new Promise((done) => server.close(done))
      });
    });
  });
}

test('normalizeBaseUrl trims trailing slashes', () => {
  assert.equal(normalizeBaseUrl('https://vcenter.lab.local///'), 'https://vcenter.lab.local');
});

test('VMware connections resolve API credentials from Credential Vault', () => {
  const owner = makeUser({ email: 'vmware-vault@test.local' });
  const credential = createCredential({
    name: 'vCenter API',
    kind: 'username_password',
    username: 'administrator@vsphere.local',
    secret: 'vault-secret',
    visibility: 'personal'
  }, owner);
  const connection = createVCenterConnection({
    name: 'Vault Backed vCenter',
    targetType: 'vcenter',
    baseUrl: 'https://vcenter.lab.local',
    credentialId: credential.id,
    apiMode: 'auto',
    requestTimeoutMs: 20000,
    allowUntrustedTls: true,
    enabled: true,
    visibility: 'personal'
  }, owner);
  const secretConnection = getVCenterConnection(connection.id, owner, true);

  assert.equal(connection.credentialId, credential.id);
  assert.equal(connection.credentialName, 'vCenter API');
  assert.equal(connection.username, 'administrator@vsphere.local');
  assert.equal(secretConnection.username, 'administrator@vsphere.local');
  assert.equal(secretConnection.password, 'vault-secret');
});

test('standalone VMware host connections use the Web Services profile', async () => {
  const { calls, restore } = installStandaloneEsxiSoapStub();
  const connection = {
    id: 'vc_host',
    name: 'ESXi 01',
    target_type: 'host',
    base_url: 'https://esxi01.lab.local/sdk',
    username: 'root',
    password: 'secret',
    enabled: 1,
    api_mode: 'api'
  };
  const settings = settingsFromVCenterConnection(connection);

  assert.equal(settings.targetType, 'host');
  assert.equal(settings.apiMode, 'web-services');
  assert.equal(settings.baseUrl, 'https://esxi01.lab.local/sdk');

  const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'ENG-ENT' } });
  restore();

  assert.equal(preview.count, 1);
  assert.ok(calls.some((call) => call.url.endsWith('/sdk/vimService.wsdl')));
  assert.ok(calls.some((call) => call.headers.Cookie?.includes('vmware_soap_session')));
  assert.equal(preview.diagnostics.standaloneHost, true);
  assert.equal(preview.candidates[0].source, 'standalone-vm');
  assert.equal(preview.candidates[0].fqdn, 'eng-ent-app01.contoso.local');
  assert.equal(preview.candidates[0].ipAddress, '10.42.8.21');
  assert.equal(preview.candidates[0].powerState, 'POWERED_ON');
});

test('standalone VMware VM candidates build managed host payloads', async () => {
  const { restore } = installStandaloneEsxiSoapStub();
  const connection = {
    id: 'vc_host',
    name: 'ESXi 01',
    target_type: 'host',
    base_url: 'https://esxi01.lab.local',
    username: 'root',
    password: 'secret',
    enabled: 1
  };
  const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'eng-ent' } });
  const payload = buildHostPayloadFromVm(preview.candidates[0], {
    connectionId: 'vc_host',
    credentialId: 'cred_1',
    transport: 'winrm',
    tags: ['lab'],
    visibility: 'shared'
  });
  const power = await getVCenterVmPowerState(connection, preview.candidates[0].id);
  restore();

  assert.equal(payload.name, 'ENG-ENT-APP01');
  assert.equal(payload.address, 'eng-ent-app01.contoso.local');
  assert.equal(payload.transport, 'winrm');
  assert.equal(payload.sourceType, 'vmware');
  assert.equal(payload.sourceConnectionId, 'vc_host');
  assert.equal(payload.sourceRef, 'vim.VirtualMachine:101');
  assert.ok(payload.tags.includes('standalone-esxi'));
  assert.ok(payload.tags.includes('esxi-vm:vim.VirtualMachine:101'));
  assert.equal(power.checked, true);
  assert.equal(power.state, 'POWERED_ON');
});

test('standalone VMware SOAP retries alternate endpoint paths after /sdk 404', async () => {
  const { calls, restore } = installStandaloneEsxiSoapStub({ sdk404: true });
  const connection = {
    id: 'vc_host_retry',
    name: 'ESXi Retry',
    target_type: 'host',
    base_url: 'https://esxi01.lab.local/sdk/vimService.wsdl',
    username: 'root',
    password: 'secret',
    enabled: 1
  };
  const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'eng-ent' } });
  restore();

  assert.equal(preview.count, 1);
  assert.ok(calls.some((call) => call.url === 'https://esxi01.lab.local/sdk'));
  assert.ok(calls.some((call) => call.url === 'https://esxi01.lab.local/sdk/'));
  assert.ok(preview.trace.some((entry) => String(entry.message || '').includes('Retrying VMware SOAP action RetrieveServiceContent')));
});

test('standalone VMware custom dispatcher path works when untrusted TLS is enabled', async () => {
  const server = await startStandaloneEsxiSoapServer();
  try {
    const connection = {
      id: 'vc_host_dispatcher',
      name: 'ESXi Dispatcher',
      target_type: 'host',
      base_url: server.baseUrl,
      username: 'root',
      password: 'secret',
      enabled: 1,
      allow_untrusted_tls: 1
    };
    const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'HTTP-ESXI' } });

    assert.equal(preview.count, 1);
    assert.equal(preview.candidates[0].name, 'HTTP-ESXI-VM01');
    assert.ok(server.requests.some((request) => request.url === '/sdk/vimService.wsdl'));
    assert.ok(server.requests.some((request) => request.cookie.includes('vmware_soap_session')));
  } finally {
    await server.close();
  }
});

test('standalone VMware host candidates build managed host payloads', () => {
  const candidate = buildStandaloneHostCandidate(
    { id: 'vc_host', name: 'ESXi 01' },
    { baseUrl: 'https://esxi01.lab.local', targetType: 'host' }
  );
  const payload = buildHostPayloadFromVm(candidate, {
    connectionId: 'vc_host',
    credentialId: 'cred_1',
    transport: 'ssh',
    tags: ['lab'],
    visibility: 'shared'
  });

  assert.equal(payload.name, 'ESXi 01');
  assert.equal(payload.address, 'esxi01.lab.local');
  assert.equal(payload.transport, 'ssh');
  assert.equal(payload.credentialId, 'cred_1');
  assert.equal(payload.sourceType, 'vmware');
  assert.equal(payload.sourceConnectionId, 'vc_host');
  assert.equal(payload.sourceRef, 'standalone-esxi-host');
  assert.ok(payload.tags.includes('standalone-esxi'));
  assert.ok(payload.tags.includes('lab'));
});

test('normalizeVCenterVm maps guest identity and networking into a host candidate', () => {
  const candidate = normalizeVCenterVm(
    { vm: 'vm-42', name: 'ENG-ENT-APP01', power_state: 'POWERED_ON' },
    { host_name: 'eng-ent-app01.contoso.local', ip_address: '10.42.1.20', full_name: { default_message: 'Microsoft Windows Server 2022' } },
    [{ ip: { ip_addresses: [{ ip_address: 'fe80::1' }, { ip_address: '10.42.1.21' }] } }]
  );

  assert.equal(candidate.id, 'vm-42');
  assert.equal(candidate.name, 'ENG-ENT-APP01');
  assert.equal(candidate.fqdn, 'eng-ent-app01.contoso.local');
  assert.equal(candidate.address, 'eng-ent-app01.contoso.local');
  assert.deepEqual(candidate.ipAddresses, ['10.42.1.20', '10.42.1.21']);
  assert.equal(candidate.osFamily, 'windows');
  assert.equal(candidate.guestFullName, 'Microsoft Windows Server 2022');
});

test('normalizeVCenterVm marks unknown guest operating systems as other', () => {
  const candidate = normalizeVCenterVm(
    { vm: 'vm-unknown', name: 'MYSTERY-01', power_state: 'POWERED_ON' },
    null,
    []
  );

  assert.equal(candidate.osFamily, 'other');
});

test('vmMatchesFilter supports hostname contains and IP equals matching', () => {
  const candidate = { name: 'ENG-ENT-APP01', fqdn: 'app01.contoso.local', address: 'app01.contoso.local', ipAddress: '10.42.1.20', ipAddresses: ['10.42.1.20'] };

  assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'eng-ent' }), true);
  assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'contoso' }), true);
  assert.equal(vmMatchesFilter(candidate, { field: 'ip', operator: 'equals', value: '10.42.1.20' }), true);
  assert.equal(vmMatchesFilter(candidate, { field: 'fqdn', operator: 'startsWith', value: 'db' }), false);
});

test('vmMatchesFilter matches guest-only host names after enrichment', () => {
  const candidate = normalizeVCenterVm(
    { vm: 'vm-200', name: 'KSUS01PAPP01', power_state: 'POWERED_ON' },
    { host_name: 'MCS-WIN-APP01.ent.mitekindustries.com', ip_address: '10.42.5.44', family: 'WINDOWS' },
    []
  );

  assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'MCS' }), true);
  assert.equal(vmMatchesFilter(candidate, { field: 'name', operator: 'contains', value: 'MCS' }), false);
});

test('filterSummaryVms scans the full VM summary list for VM-name filters', () => {
  const vms = [
    ...Array.from({ length: 900 }, (_, index) => ({ vm: `vm-${index}`, name: `ZZZ-${index}` })),
    { vm: 'vm-901', name: 'MCS-WIN-APP01' }
  ];
  const result = filterSummaryVms(vms, { field: 'name', operator: 'contains', value: 'MCS' });

  assert.equal(result.summaryFilterApplied, true);
  assert.equal(result.summaryMatched, 1);
  assert.equal(result.vms.length, 1);
  assert.equal(result.vms[0].name, 'MCS-WIN-APP01');
});

test('filterSummaryVms prefilters hostname searches by VM name and falls back when needed', () => {
  const vms = [{ vm: 'vm-1', name: 'MCS-WIN-APP01' }, { vm: 'vm-2', name: 'SQL-01' }];
  const matched = filterSummaryVms(vms, { field: 'hostname', operator: 'contains', value: 'MCS' });
  const fallback = filterSummaryVms(vms, { field: 'hostname', operator: 'contains', value: 'guest-only-name' });

  assert.equal(matched.summaryMatched, 1);
  assert.equal(matched.summaryFilterFallback, false);
  assert.deepEqual(matched.vms.map((vm) => vm.vm), ['vm-1', 'vm-2']);
  assert.equal(fallback.summaryMatched, 0);
  assert.equal(fallback.summaryFilterFallback, true);
  assert.equal(fallback.vms.length, 2);
});

test('buildVmListPath uses documented exact-name filters for api and rest profiles', () => {
  const filter = { field: 'name', operator: 'equals', value: 'MCS-WIN-APP01' };

  assert.equal(
    buildVmListPath({ vmListPath: '/api/vcenter/vm', vmNameParam: 'names' }, filter),
    '/api/vcenter/vm?names=MCS-WIN-APP01'
  );
  assert.equal(
    buildVmListPath({ vmListPath: '/rest/vcenter/vm', vmNameParam: 'filter.names' }, filter),
    '/rest/vcenter/vm?filter.names=MCS-WIN-APP01'
  );
  assert.equal(
    buildVmListPath({ vmListPath: '/api/vcenter/vm', vmNameParam: 'names' }, { field: 'name', operator: 'contains', value: 'MCS' }),
    '/api/vcenter/vm'
  );
});

test('buildHostPayloadFromVm attaches credential, tags, transport, and import notes', () => {
  const payload = buildHostPayloadFromVm(
    {
      id: 'vm-42',
      name: 'ENG-ENT-APP01',
      fqdn: 'eng-ent-app01.contoso.local',
      address: 'eng-ent-app01.contoso.local',
      osFamily: 'windows',
      powerState: 'POWERED_ON',
      guestFullName: 'Microsoft Windows Server 2022',
      ipAddresses: ['10.42.1.20']
    },
    { credentialId: 'cred_1', connectionId: 'vc_1', transport: 'winrm', tags: ['engineering'], visibility: 'group', groupId: 'grp_1' }
  );

  assert.equal(payload.credentialId, 'cred_1');
  assert.equal(payload.transport, 'winrm');
  assert.equal(payload.visibility, 'group');
  assert.equal(payload.groupId, 'grp_1');
  assert.equal(payload.sourceType, 'vmware');
  assert.equal(payload.sourceConnectionId, 'vc_1');
  assert.equal(payload.sourceRef, 'vm-42');
  assert.ok(payload.tags.includes('vcenter:vm-42'));
  assert.ok(payload.tags.includes('engineering'));
  assert.match(payload.notes, /Imported from VMware vCenter VM vm-42/);
});
