/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import {FC, useCallback, useEffect, useState, Children} from 'react';
import {TestCaseResultType} from '../core';
import {Filter, TestingContext} from './TestingContext';
import {PALETTE} from './palette';
import {
  Alert,
  ScrollView,
  StyleSheet,
  Text,
  View,
  ViewStyle,
} from 'react-native';

const defaultTestingSummary: Record<TestCaseResultType | 'total', number> = {
  total: 0,
  pass: 0,
  fail: 0,
  broken: 0,
  example: 0,
  skipped: 0,
  waitingForTester: 0,
};

export const Tester: FC<{
  style?: ViewStyle;
  children: any;
  filter?: Filter;
  sequential?: boolean | {pauseOnFailure: boolean};
}> = ({children, filter, style, sequential = false}) => {
  const [testCaseResultTypeByTestCaseId, setTestCaseResultTypeByTestCaseId] =
    useState<Record<string, TestCaseResultType | 'unknown'>>({});
  const [testingSummary, setTestingSummary] = useState({
    ...defaultTestingSummary,
  });
  const [currentChildIndex, setCurrentChildIndex] = useState(0);
  const [displaySummaryPage, setDisplaySummaryPage] = useState(false);
  const childArray = Children.toArray(children);
  const registerTestCase = useCallback((testCaseId: string) => {
    setTestCaseResultTypeByTestCaseId(prev => {
      if (testCaseId in prev) {
        console.warn(`Test ${testCaseId} already exists`);
      }
      return {
        ...prev,
        [testCaseId]: 'unknown',
      };
    });
  }, []);

  const reportTestCaseResult = useCallback(
    (testCaseId: string, result: TestCaseResultType) => {
      /**
       * delay reporting test case result to avoid race with registerTestCase
       */
      setTimeout(() => {
        setTestCaseResultTypeByTestCaseId(
          currentTestCaseResultTypeByTestCaseId => {
            if (!(testCaseId in currentTestCaseResultTypeByTestCaseId)) {
              console.warn(
                `Reported test case result but test case wasn't registered: ${testCaseId}`,
              );
            }
            return {
              ...currentTestCaseResultTypeByTestCaseId,
              [testCaseId]: result,
            };
          },
        );
      }, 0);
    },
    [],
  );

  useEffect(() => {
    const newTestingSummary = {...defaultTestingSummary};
    for (const [_testCaseId, testCaseResult] of Object.entries(
      testCaseResultTypeByTestCaseId,
    )) {
      if (testCaseResult !== 'unknown') {
        newTestingSummary[testCaseResult]++;
      }
      newTestingSummary.total++;
    }
    setTestingSummary(newTestingSummary);
  }, [testCaseResultTypeByTestCaseId]);

  const changeRenderedTestsIfSequential = () => {
    if (!sequential) {
      return;
    }
    if (currentChildIndex !== childArray.length - 1) {
      /**
       * setTimeout is needed here to bypass React Native infinite loop detector
       */
      setTimeout(() => {
        setCurrentChildIndex(currentChildIndex + 1);
      }, 0);
    } else {
      setDisplaySummaryPage(true);
    }
  };

  return (
    <TestingContext.Provider
      value={{
        registerTestCase,
        reportTestCaseResult,
        onTestSuiteComplete: changeRenderedTestsIfSequential,
        isSequential: sequential,
        filter: filter ?? (() => true),
      }}>
      <View style={StyleSheet.compose(styles.testerContainer, style)}>
        <ScrollView
          horizontal
          style={styles.summaryContainer}
          contentContainerStyle={{alignItems: 'center', width: '100%'}}>
          {!sequential && (
            <>
              <SummaryItem
                name="TOTAL"
                color={'white'}
                value={testingSummary.total}
              />
              <SummaryItem
                name="RUNNING"
                color={PALETTE.red}
                value={
                  testingSummary.total -
                  (testingSummary.pass +
                    testingSummary.fail +
                    testingSummary.waitingForTester +
                    testingSummary.example +
                    testingSummary.broken +
                    testingSummary.skipped)
                }
                onPress={() => {
                  const msg = Object.entries(
                    testCaseResultTypeByTestCaseId,
                  ).reduce((acc, [testCaseId, result]) => {
                    if (result !== 'unknown') {
                      return acc;
                    }
                    return acc + testCaseId + '\n';
                  }, '');
                  Alert.alert('Running Test Cases', msg || '—');
                }}
              />
            </>
          )}
          {sequential && (
            <SummaryItem
              name="FINISHED"
              color={'white'}
              value={testingSummary.total}
            />
          )}
          <SummaryItem
            name="PASS"
            color={PALETTE.green}
            value={testingSummary.pass}
          />
          {!sequential && (
            <SummaryItem
              name="WAITING"
              color={PALETTE.blue}
              value={testingSummary.waitingForTester}
            />
          )}
          <SummaryItem
            name="SKIPPED"
            color={PALETTE.yellow}
            value={testingSummary.skipped}
          />
          <SummaryItem
            name="FAIL"
            color={PALETTE.red}
            value={testingSummary.fail}
            onPress={() => {
              const msg = Object.entries(testCaseResultTypeByTestCaseId).reduce(
                (acc, [testCaseId, result]) => {
                  if (result !== 'fail') {
                    return acc;
                  }
                  return acc + testCaseId + '\n';
                },
                '',
              );
              Alert.alert('Failing Test Cases', msg || '—');
            }}
          />
          <SummaryItem
            name="BROKEN"
            color={PALETTE.red}
            value={testingSummary.broken}
            onPress={() => {
              const msg = Object.entries(testCaseResultTypeByTestCaseId).reduce(
                (acc, [testCaseId, result]) => {
                  if (result !== 'broken') {
                    return acc;
                  }
                  return acc + testCaseId + '\n';
                },
                '',
              );
              Alert.alert('Broken Test Cases', msg || '—');
            }}
          />
          <SummaryItem
            name="EXAMPLES"
            color={PALETTE.gray}
            value={testingSummary.example}
          />
        </ScrollView>
        {!sequential ? (
          children
        ) : displaySummaryPage ? (
          <SummaryPage testCases={testCaseResultTypeByTestCaseId} />
        ) : (
          childArray[currentChildIndex]
        )}
      </View>
    </TestingContext.Provider>
  );
};

const SummaryItem: FC<{
  name: string;
  color: string;
  value: number;
  onPress?: () => void;
}> = ({name, color, value, onPress}) => {
  return (
    <View style={styles.summaryItemContainer} onTouchEnd={() => onPress?.()}>
      <Text
        testID={'TESTERINO_' + name + '_VALUE'}
        style={[
          styles.summaryItemValue,
          {color: value > 0 ? color : PALETTE.gray},
        ]}
        numberOfLines={1}>
        {value}
      </Text>
      <Text style={styles.summaryItemName}>{name}</Text>
    </View>
  );
};

const SummaryPage: FC<{
  testCases: Record<string, TestCaseResultType | 'unknown'>;
}> = ({testCases}) => {
  return (
    <ScrollView>
      {Object.entries(testCases).map(([name, result]) => (
        <SingleTestSummary key={name} name={name} status={result} />
      ))}
    </ScrollView>
  );
};

const SingleTestSummary: FC<{
  name: string;
  status: TestCaseResultType | 'unknown';
}> = ({name, status}) => {
  const labelInfo = STATUS_LABEL_DATA_BY_TEST_RESULT[status];
  return (
    <View style={styles.testCaseHeaderContainer}>
      <Text style={styles.testCaseHeader}>{name}</Text>
      <Text style={[styles.testCaseStatus, {color: labelInfo.color}]}>
        {status}
      </Text>
    </View>
  );
};

const STATUS_LABEL_DATA_BY_TEST_RESULT: Record<
  TestCaseResultType | 'unknown',
  {label: string; color: string}
> = {
  broken: {label: 'BROKEN', color: PALETTE.red},
  fail: {label: 'FAIL', color: PALETTE.red},
  pass: {label: 'PASS', color: PALETTE.green},
  skipped: {label: 'SKIPPED', color: PALETTE.yellow},
  waitingForTester: {label: 'WAITING', color: PALETTE.blue},
  unknown: {label: 'UNKNOWN', color: PALETTE.gray},
  example: {label: 'EXAMPLE', color: PALETTE.blue},
};

const styles = StyleSheet.create({
  testerContainer: {
    backgroundColor: '#222',
    paddingHorizontal: 8,
  },
  summaryContainer: {
    width: '100%',
    flexDirection: 'row',
    paddingHorizontal: 8,
    flexGrow: 0,
    paddingBottom: 4,
  },
  summaryItemContainer: {
    flex: 1,
    paddingHorizontal: 1,
  },
  summaryItemValue: {
    color: 'white',
    fontSize: 20,
    width: '100%',
    fontWeight: 'bold',
    textAlign: 'center',
  },
  summaryItemName: {
    color: PALETTE.gray,
    width: '100%',
    fontSize: 7,
    textAlign: 'center',
  },
  testCaseStatus: {
    width: 72,
    height: '100%',
    fontSize: 12,
    fontWeight: 'bold',
    textAlign: 'right',
    color: '#AAA',
  },
  testCaseHeaderContainer: {
    width: '100%',
    display: 'flex',
    flexDirection: 'row',
    borderTopWidth: 1,
    borderColor: '#666',
  },
  testCaseHeader: {
    flex: 1,
    height: '100%',
    fontSize: 12,
    color: '#EEE',
  },
});