import 'dart:io';

import 'package:dart_style/dart_style.dart';
import 'package:paraphrase/paraphrase.dart';
import 'package:testcase_gen/generator.dart';
import 'package:meta/meta.dart';

const ignoreForFile = '// ignore_for_file: '
    'deprecated_member_use,'
    'constant_identifier_names';

const defaultHeader = '''
/// GENERATED BY testcase_gen. DO NOT MODIFY BY HAND.

$ignoreForFile
''';

abstract class DefaultGenerator implements Generator {
  const DefaultGenerator();

  @protected
  GeneratorConfig? getConfig(List<GeneratorConfig> configs, String methodName) {
    for (final config in configs) {
      if (config.name == methodName) {
        return config;
      }
    }
    return null;
  }

  String _concatParamName(String? prefix, String name) {
    if (prefix == null) return name;
    return '$prefix${name[0].toUpperCase()}${name.substring(1)}';
  }

  @protected
  String getParamType(Parameter parameter) {
    if (parameter.type.typeArguments.isEmpty) {
      return parameter.type.type;
    }

    return '${parameter.type.type}<${parameter.type.typeArguments.join(', ')}>';
  }

  bool _isPrimitiveType(Type type) =>
      type.type == 'int' ||
      type.type == 'double' ||
      type.type == 'bool' ||
      type.type == 'String' ||
      type.type == 'List' ||
      type.type == 'Map' ||
      type.type == 'Set' ||
      type.type == 'Uint8List';

  String defualtValueOfType(
    Type type,
  ) {
    switch (type.type) {
      case 'int':
        return '5';
      case 'double':
        return '5.0';
      case 'String':
        return '"hello"';
      case 'bool':
        return 'true';
      case 'List':
        if (type.typeArguments.isNotEmpty) {
          final typeArgumentType = Type()..type = type.typeArguments[0];
          if (_isPrimitiveType(typeArgumentType)) {
            return 'List.filled(5, ${defualtValueOfType(typeArgumentType)})';
          } else {
            return '[]';
          }
        }
        return '[]';
      case 'Map':
        return '{}';
      case 'Uint8List':
        return 'Uint8List.fromList([1, 1, 1, 1, 1])';
      case 'Set':
        return '{}';

      default:
        throw Exception('not support type $type');
    }
  }

  @protected
  String createListBuilderBlockForList(
      ParseResult parseResult, Parameter parameter) {
    if (parameter.type.typeArguments.isNotEmpty) {
      final listTypeArgumentType = parameter.type.typeArguments[0];
      final listTypeArgumentTypeClazzes =
          parseResult.getClazz(listTypeArgumentType);
      if (listTypeArgumentTypeClazzes.isNotEmpty) {
        final clazz = listTypeArgumentTypeClazzes[0];
        final toParamType = Type()..type = clazz.name;
        Parameter pp = Parameter()
          ..type = toParamType
          ..name = '${parameter.name}Item';
        final listInitializerBuilder = StringBuffer();
        createConstructorInitializerForMethodParameter(
            parseResult, null, pp, listInitializerBuilder);
        final listBuilder = '''
final ${getParamType(parameter)} ${parameter.name} = () {
${listInitializerBuilder.toString()}

  return List.filled(5, ${parameter.name}Item);
}();
''';
        return listBuilder;
      }
    }
    return '';
  }

  @protected
  String createConstructorInitializerForMethodParameter(
    ParseResult parseResult,
    Parameter? rootParameter,
    Parameter parameter,
    StringBuffer initializerBuilder,
  ) {
    final bool isEnum = parseResult.hasEnum(parameter.type.type);

    if (isEnum) {
      final enumz = parseResult.getEnum(parameter.type.type)[0];

      initializerBuilder.writeln(
          '${getParamType(parameter)} ${_concatParamName(rootParameter?.name, parameter.name)} = ${enumz.enumConstants[0].name};');

      return _concatParamName(rootParameter?.name, parameter.name);
    }

    stdout.writeln('parameter.type.type: ${parameter.type.type}');
    final parameterClass = parseResult.getClazz(parameter.type.type)[0];
    final initBlockParameterListBuilder = StringBuffer();
    final initBlockBuilder = StringBuffer();

    bool isNullable = false;
    if (parameterClass.constructors.isEmpty) {
      // If there're not constructors found, default to null.
      isNullable = true;
      initBlockBuilder.write('null');
    } else {
      final constructor = parameterClass.constructors[0];

      initBlockBuilder.write(parameterClass.name);
      initBlockBuilder.write('(');

      for (final cp in parameterClass.constructors[0].parameters) {
        final adjustedParamName = _concatParamName(parameter.name, cp.name);
        if (cp.isNamed) {
          if (cp.type.type == 'Function') {
            stdout.writeln(
                'cp.type.parameters: ${cp.type.parameters.map((e) => e.name.toString()).toString()}');
            final functionParamsList = cp.type.parameters
                .map((t) => '${t.type.type} ${t.name}')
                .join(', ');

            initBlockBuilder.write('${cp.name}:($functionParamsList) { },');
          } else if (cp.isPrimitiveType) {
            if (getParamType(cp) == 'Uint8List') {
              initBlockParameterListBuilder.writeln(
                  '${getParamType(cp)} $adjustedParamName = ${defualtValueOfType(cp.type)};');
            } else {
              if (getParamType(cp).startsWith('List') &&
                  parameter.type.typeArguments.isNotEmpty) {
                final listBuilderBlock =
                    createListBuilderBlockForList(parseResult, parameter);
                initBlockParameterListBuilder.writeln(listBuilderBlock);
              } else {
                initBlockParameterListBuilder.writeln(
                    '${getParamType(cp)} $adjustedParamName = ${defualtValueOfType(cp.type)};');
              }
            }

            initBlockBuilder.write('${cp.name}: $adjustedParamName,');
          } else {
            createConstructorInitializerForMethodParameter(
                parseResult, parameter, cp, initializerBuilder);
            initBlockBuilder.write('${cp.name}: $adjustedParamName,');
          }
        } else {
          if (cp.type.type == 'Function') {
            final functionParamsList = cp.type.parameters
                .map((t) => '${t.type.type} ${t.name}')
                .join(', ');

            initBlockBuilder.write('${cp.name}:($functionParamsList) { },');
          } else if (cp.isPrimitiveType) {
            if (getParamType(cp) == 'Uint8List') {
              initBlockParameterListBuilder.writeln(
                  '${getParamType(cp)} $adjustedParamName = ${defualtValueOfType(cp.type)};');
            } else {
              if (getParamType(cp).startsWith('List') &&
                  parameter.type.typeArguments.isNotEmpty) {
                final listBuilderBlock =
                    createListBuilderBlockForList(parseResult, parameter);
                initBlockParameterListBuilder.writeln(listBuilderBlock);
              } else {
                initBlockParameterListBuilder.writeln(
                    '${getParamType(cp)} $adjustedParamName = ${defualtValueOfType(cp.type)};');
              }
            }

            initBlockBuilder.write('$adjustedParamName,');
          } else {
            createConstructorInitializerForMethodParameter(
                parseResult, parameter, cp, initializerBuilder);
            initBlockBuilder.write('$adjustedParamName,');
          }
        }
      }

      initBlockBuilder.write(')');
    }

    initializerBuilder.write(initBlockParameterListBuilder.toString());
    // final keywordPrefix = shouldBeConst ? 'const' : 'final';

    initializerBuilder.writeln(
        '${getParamType(parameter)}${isNullable ? '?' : ''} ${_concatParamName(rootParameter?.name, parameter.name)} = ${initBlockBuilder.toString()};');
    return _concatParamName(rootParameter?.name, parameter.name);
  }

  String generateWithTemplate({
    required ParseResult parseResult,
    required Clazz clazz,
    required String testCaseTemplate,
    required String testCasesContentTemplate,
    required String methodInvokeObjectName,
    required List<GeneratorConfig> configs,
    List<GeneratorConfigPlatform>? supportedPlatformsOverride,
    List<String> skipMemberFunctions = const [],
  }) {
    final testCases = <String>[];
    for (final method in clazz.methods) {
      final methodName = method.name;

      if (skipMemberFunctions.contains(methodName)) {
        continue;
      }

      final config = getConfig(configs, methodName);
      if (config?.donotGenerate == true) continue;
      if (methodName.startsWith('_')) continue;
      if (methodName.startsWith('create')) continue;

      StringBuffer pb = StringBuffer();

      for (final parameter in method.parameters) {
        if (parameter.type.type == 'Function') {
          continue;
        }
        if (parameter.isPrimitiveType) {
          final parameterType = getParamType(parameter);
          if (parameterType == 'Uint8List') {
            pb.writeln(
                '${getParamType(parameter)} ${parameter.name} = ${defualtValueOfType(parameter.type)};');
          } else {
            if (parameterType.startsWith('List') &&
                parameter.type.typeArguments.isNotEmpty) {
              final listBuilderBlock =
                  createListBuilderBlockForList(parseResult, parameter);
              pb.writeln(listBuilderBlock);
            } else {
              pb.writeln(
                  '${getParamType(parameter)} ${parameter.name} = ${defualtValueOfType(parameter.type)};');
            }
          }
        } else {
          createConstructorInitializerForMethodParameter(
              parseResult, null, parameter, pb);
        }
      }

      StringBuffer methodCallBuilder = StringBuffer();
      bool isFuture = method.returnType.type == 'Future';
      // methodCallBuilder.write('await screenShareHelper.$methodName(');
      methodCallBuilder.write(
          '${isFuture ? 'await ' : ''}$methodInvokeObjectName.$methodName(');
      for (final parameter in method.parameters) {
        if (parameter.isNamed) {
          methodCallBuilder.write('${parameter.name}:${parameter.name},');
        } else {
          methodCallBuilder.write('${parameter.name}, ');
        }
      }
      methodCallBuilder.write(');');

      pb.writeln(methodCallBuilder.toString());

      String skipExpression = 'false';

      if (supportedPlatformsOverride != null) {
        // skipExpression =
        //     '!(${desktopPlatforms.map((e) => e.toPlatformExpression()).join(' || ')})';
        skipExpression =
            '!(${supportedPlatformsOverride.map((e) => e.toPlatformExpression()).join(' || ')})';
      } else {
        if (config != null &&
            config.supportedPlatforms.length <
                GeneratorConfigPlatform.values.length) {
          skipExpression =
              '!(${config.supportedPlatforms.map((e) => e.toPlatformExpression()).join(' || ')})';
        }
      }

      String testCase = testCaseTemplate.replaceAll(
          '{{TEST_CASE_NAME}}', '${clazz.name}.$methodName');
      testCase = testCase.replaceAll('{{TEST_CASE_BODY}}', pb.toString());
      testCase = testCase.replaceAll('{{TEST_CASE_SKIP}}', skipExpression);
      testCases.add(testCase);
    }

    final output = testCasesContentTemplate.replaceAll(
      '{{TEST_CASES_CONTENT}}',
      testCases.join('\n'),
    );

    return DartFormatter().format(output);
  }
}
