import { TestSuiteReport, TestSuite, TestCase } from './TestResults';

/**
 * 
 * @param report A report in the default schema, as supported by GitLab
 */
export function getXmlObject(report: TestSuiteReport) {
  const testSuites = report.suites.map(attachTestSuiteMetadata);

  const xmlObject: { testsuites: any[] } = {
    testsuites: testSuites.map((testSuite) => ({ testsuite: getTestSuiteXmlObject(testSuite) })),
  };

  const testsuitesAttributes: { [key: string]: string } = {
    tests: testSuites.reduce((tests, suite) => tests + suite.tests, 0).toString(),
    errors: testSuites.reduce((errors, suite) => errors + suite.errors, 0).toString(),
    failures: testSuites.reduce((failures, suite) => failures + suite.failures, 0).toString(),
    skipped: testSuites.reduce((skipped, suite) => skipped + suite.skipped, 0).toString(),
  };
  if (report.name) {
    testsuitesAttributes.name = report.name;
  }
  if (report.time) {
    testsuitesAttributes.time = report.time.toString();
  }
  xmlObject.testsuites.push({ _attr: testsuitesAttributes });

  return xmlObject;
}

function getTestSuiteXmlObject(testSuite: TestSuite & TestSuiteMetadata) {
  const testSuiteXmlObject: any[] = testSuite.testCases
    .map((testCase) => ({ testcase: getTestCaseXmlObject(testCase) }));

  const testsuiteAttributes: { [key: string]: string } = {
    tests: testSuite.tests.toString(),
    errors: testSuite.errors.toString(),
    failures: testSuite.failures.toString(),
    skipped: testSuite.skipped.toString(),
  };
  if (testSuite.name) {
    testsuiteAttributes.name = testSuite.name;
  }
  if (testSuite.hostname) {
    testsuiteAttributes.hostname = testSuite.hostname;
  }
  if (testSuite.time) {
    testsuiteAttributes.time = testSuite.time.toString();
  }
  if (testSuite.timestamp) {
    testsuiteAttributes.timestamp = testSuite.timestamp.toISOString();
  }
  testSuiteXmlObject.push({ _attr: testsuiteAttributes });

  return testSuiteXmlObject;
}

function getTestCaseXmlObject(testCase: TestCase) {
  const testCaseXmlObject: any[] = [];

  const testcaseAttributes: { [key: string]: string } = {
    name: testCase.name,
    classname: testCase.classname || testCase.name,
  };
  if (testCase.time) {
    testcaseAttributes.time = testCase.time.toString();
  }
  if (testCase.assertions) {
    testcaseAttributes.assertions = testCase.assertions.toString();
  }
  testCaseXmlObject.push({ _attr: testcaseAttributes });

  if (testCase.skipped) {
    testCaseXmlObject.push({ skipped: '' });
  }

  const errors = (Array.isArray(testCase.errors))
    ? testCase.errors.map(error => ({ error: formatMessage(error) }))
    : [];

  const failures = (Array.isArray(testCase.failures))
    ? testCase.failures.map(failure => ({ error: formatMessage(failure) }))
    : [];

  const systemOuts = (Array.isArray(testCase.systemOut))
    ? testCase.systemOut.map(systemOut => ({ 'system-out': systemOut }))
    : [];

  const systemErrs = (Array.isArray(testCase.systemErr))
    ? testCase.systemErr.map(systemErr => ({ 'system-err': systemErr }))
    : [];

  return testCaseXmlObject.concat(errors).concat(failures).concat(systemOuts).concat(systemErrs);
}

interface TestSuiteMetadata {
  tests: number;
  failures: number;
  errors: number;
  skipped: number;
};
function attachTestSuiteMetadata(testSuite: TestSuite): TestSuite & TestSuiteMetadata {
  return {
    ...testSuite,
    tests: testSuite.testCases.length,
    failures: testSuite.testCases.filter(isFailing).length,
    errors: testSuite.testCases.filter(hasErrors).length,
    skipped: testSuite.testCases.filter(isSkipped).length,
  };
}

function isSkipped(testCase: TestCase): boolean {
  return testCase.skipped === true;
}

function isFailing(testCase: TestCase): boolean {
  return Array.isArray(testCase.failures) && testCase.failures.length > 0;
}

function hasErrors(testCase: TestCase): boolean {
  return Array.isArray(testCase.errors) && testCase.errors.length > 0;
}

function formatMessage(message: { message: string; type?: string }) {
  if (typeof message.type === 'string') {
    return [ message.message, { _attr: { type: message.type } } ];
  }

  return message.message;
}
