// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

/// Tests FocusFlow runtime config and file logging behavior.
library;

import 'dart:convert';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:focus_flow_flutter/app/runtime/focus_flow_file_logger.dart';
import 'package:focus_flow_flutter/app/runtime/focus_flow_runtime_config.dart';
import 'package:scheduler_core/scheduler_core.dart' as scheduler_core;

/// Runs runtime config tests.
void main() {
  tearDown(scheduler_core.logger.disable);

  group('FocusFlowRuntimeConfig', () {
    test('missing config file leaves optional settings unset', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-config-test-',
      );
      addTearDown(() => directory.delete(recursive: true));

      final config = await FocusFlowRuntimeConfig.load(
        path: '${directory.path}${Platform.pathSeparator}missing.json',
      );

      expect(config.logLevel, isNull);
      expect(config.logFileLocation, isNull);
      expect(config.enablesFileLogging, isFalse);
    });

    test('loads supported settings from a config file', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-config-file-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final configFile = File(
        '${directory.path}${Platform.pathSeparator}config.json',
      );
      await configFile.writeAsString(
        jsonEncode({
          'LogLevel': 'finest',
          'LogfileLocation': directory.path,
          'Timezone': 'pDt',
        }),
      );

      final config = await FocusFlowRuntimeConfig.load(
        path: configFile.path,
        now: () => DateTime.utc(2026, 7, 3, 12),
      );

      expect(config.logLevel, FocusFlowLogLevel.finest);
      expect(config.logFileLocation, directory.path);
      expect(config.timeZone?.code, 'PDT');
      expect(config.timeZone?.utcOffset, const Duration(hours: -7));
      expect(config.enablesFileLogging, isTrue);
    });

    test('resolves daylight-saving aliases from the reference instant', () {
      final pstInSummer = FocusFlowUiTimeZone.tryParse(
        'PST',
        referenceInstant: DateTime.utc(2026, 7, 5, 2),
      );
      final pdtInWinter = FocusFlowUiTimeZone.tryParse(
        'pdt',
        referenceInstant: DateTime.utc(2026, 1, 5, 12),
      );
      final cstInSummer = FocusFlowUiTimeZone.tryParse(
        'CST',
        referenceInstant: DateTime.utc(2026, 7, 5, 12),
      );

      expect(pstInSummer?.code, 'PDT');
      expect(pstInSummer?.utcOffset, const Duration(hours: -7));
      expect(pdtInWinter?.code, 'PST');
      expect(pdtInWinter?.utcOffset, const Duration(hours: -8));
      expect(cstInSummer?.code, 'CDT');
      expect(cstInSummer?.utcOffset, const Duration(hours: -5));
    });

    test('keeps non-DST timezone codes fixed without regard to case', () {
      final gmt = FocusFlowUiTimeZone.tryParse(
        'GmT',
        referenceInstant: DateTime.utc(2026, 7, 5, 12),
      );
      final jst = FocusFlowUiTimeZone.tryParse(
        'jst',
        referenceInstant: DateTime.utc(2026, 7, 5, 12),
      );

      expect(gmt?.code, 'GMT');
      expect(gmt?.utcOffset, Duration.zero);
      expect(jst?.code, 'JST');
      expect(jst?.utcOffset, const Duration(hours: 9));
    });

    test('resolves daylight-saving boundaries at config parse time', () {
      final beforePacificSpring = FocusFlowUiTimeZone.tryParse(
        'PST',
        referenceInstant: DateTime.utc(2026, 3, 8, 9, 59),
      );
      final afterPacificSpring = FocusFlowUiTimeZone.tryParse(
        'PST',
        referenceInstant: DateTime.utc(2026, 3, 8, 10),
      );
      final beforePacificFall = FocusFlowUiTimeZone.tryParse(
        'PDT',
        referenceInstant: DateTime.utc(2026, 11, 1, 8, 59),
      );
      final afterPacificFall = FocusFlowUiTimeZone.tryParse(
        'PDT',
        referenceInstant: DateTime.utc(2026, 11, 1, 9),
      );

      expect(beforePacificSpring?.code, 'PST');
      expect(afterPacificSpring?.code, 'PDT');
      expect(beforePacificFall?.code, 'PDT');
      expect(afterPacificFall?.code, 'PST');
    });

    test('ignores timezone values that are not valid three-letter codes', () {
      expect(FocusFlowUiTimeZone.tryParse('America/Los_Angeles'), isNull);
      expect(FocusFlowUiTimeZone.tryParse('PT'), isNull);
      expect(FocusFlowUiTimeZone.tryParse('XYZ'), isNull);
      expect(FocusFlowUiTimeZone.tryParse('  '), isNull);
    });

    test('parses every supported log level', () {
      expect(FocusFlowLogLevel.tryParse('finest'), FocusFlowLogLevel.finest);
      expect(FocusFlowLogLevel.tryParse('finer'), FocusFlowLogLevel.finer);
      expect(FocusFlowLogLevel.tryParse('fine'), FocusFlowLogLevel.fine);
      expect(FocusFlowLogLevel.tryParse('debug'), FocusFlowLogLevel.debug);
      expect(FocusFlowLogLevel.tryParse('info'), FocusFlowLogLevel.info);
      expect(FocusFlowLogLevel.tryParse('warn'), FocusFlowLogLevel.warn);
      expect(FocusFlowLogLevel.tryParse('error'), FocusFlowLogLevel.error);
    });

    test('loads supported logging settings from JSON', () {
      final config = FocusFlowRuntimeConfig.fromJson({
        'LogLevel': 'debug',
        'LogfileLocation': '/tmp/focus-flow-logs',
        'Timezone': 'GMT',
      });

      expect(config.logLevel, FocusFlowLogLevel.debug);
      expect(config.logFileLocation, '/tmp/focus-flow-logs');
      expect(config.timeZone?.code, 'GMT');
      expect(config.enablesFileLogging, isTrue);
    });

    test('ignores missing and unsupported optional settings', () {
      final config = FocusFlowRuntimeConfig.fromJson({
        'LogLevel': 'verbose',
        'LogfileLocation': '   ',
        'Timezone': 'Pacific',
      });

      expect(config.logLevel, isNull);
      expect(config.logFileLocation, isNull);
      expect(config.timeZone, isNull);
      expect(config.enablesFileLogging, isFalse);
    });
  });

  group('FocusFlowFileLogger', () {
    test('does nothing unless both logging settings are set', () async {
      final logger = await FocusFlowFileLogger.open(
        const FocusFlowRuntimeConfig(logLevel: FocusFlowLogLevel.debug),
      );

      expect(logger.isEnabled, isFalse);
      await logger.info('ignored');
    });

    test('writes messages at or above the configured level', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-log-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final logger = await FocusFlowFileLogger.open(
        FocusFlowRuntimeConfig(
          logLevel: FocusFlowLogLevel.warn,
          logFileLocation: directory.path,
        ),
        now: () => DateTime.utc(2026, 7, 3, 12),
      );

      await logger.info('not written');
      await logger.warn('written warning');
      await logger.error('written error', error: 'boom');

      final logFile = File(
        '${directory.path}${Platform.pathSeparator}focus_flow.log',
      );
      final contents = await logFile.readAsString();
      expect(contents, isNot(contains('not written')));
      expect(contents, contains('2026-07-03T12:00:00.000Z WARN'));
      expect(contents, contains('written warning'));
      expect(contents, contains('ERROR written error | error=boom'));
    });

    test('skips lazy message work below the configured level', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-lazy-skip-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final logger = await FocusFlowFileLogger.open(
        FocusFlowRuntimeConfig(
          logLevel: FocusFlowLogLevel.info,
          logFileLocation: directory.path,
        ),
      );
      var evaluated = false;

      await logger.debug(() {
        evaluated = true;
        return 'expensive debug state';
      });

      expect(evaluated, isFalse);
      final contents = await File(
        '${directory.path}${Platform.pathSeparator}focus_flow.log',
      ).readAsString();
      expect(contents, isNot(contains('expensive debug state')));
    });

    test('evaluates lazy messages once the level is enabled', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-lazy-write-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final logger = await FocusFlowFileLogger.open(
        FocusFlowRuntimeConfig(
          logLevel: FocusFlowLogLevel.debug,
          logFileLocation: directory.path,
        ),
        now: () => DateTime.utc(2026, 7, 3, 12),
      );
      var evaluations = 0;

      await logger.debug(() {
        evaluations += 1;
        return 'computed debug state';
      });

      expect(evaluations, 1);
      final contents = await File(
        '${directory.path}${Platform.pathSeparator}focus_flow.log',
      ).readAsString();
      expect(contents, contains('DEBUG computed debug state'));
    });

    test(
      'finest captures caller information for every written level',
      () async {
        final directory = await Directory.systemTemp.createTemp(
          'focus-flow-finest-caller-test-',
        );
        addTearDown(() => directory.delete(recursive: true));
        final logger = await FocusFlowFileLogger.open(
          FocusFlowRuntimeConfig(
            logLevel: FocusFlowLogLevel.finest,
            logFileLocation: directory.path,
          ),
          now: () => DateTime.utc(2026, 7, 3, 12),
        );

        await logger.info('high level message');

        final contents = await File(
          '${directory.path}${Platform.pathSeparator}focus_flow.log',
        ).readAsString();
        expect(contents, contains('INFO caller='));
        expect(contents, contains('runtime_config_test.dart'));
        expect(contents, contains('high level message'));
      },
    );

    test('fine captures caller information for warnings and errors', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-fine-caller-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final logger = await FocusFlowFileLogger.open(
        FocusFlowRuntimeConfig(
          logLevel: FocusFlowLogLevel.fine,
          logFileLocation: directory.path,
        ),
        now: () => DateTime.utc(2026, 7, 3, 12),
      );

      await logger.warn('handled warning');

      final contents = await File(
        '${directory.path}${Platform.pathSeparator}focus_flow.log',
      ).readAsString();
      expect(contents, contains('WARN caller='));
      expect(contents, contains('runtime_config_test.dart'));
      expect(contents, contains('handled warning'));
    });

    test('info does not capture caller information for warnings', () async {
      final directory = await Directory.systemTemp.createTemp(
        'focus-flow-info-caller-test-',
      );
      addTearDown(() => directory.delete(recursive: true));
      final logger = await FocusFlowFileLogger.open(
        FocusFlowRuntimeConfig(
          logLevel: FocusFlowLogLevel.info,
          logFileLocation: directory.path,
        ),
        now: () => DateTime.utc(2026, 7, 3, 12),
      );

      await logger.warn('minimal warning');

      final contents = await File(
        '${directory.path}${Platform.pathSeparator}focus_flow.log',
      ).readAsString();
      expect(contents, contains('WARN minimal warning'));
      expect(contents, isNot(contains('caller=')));
      expect(contents, isNot(contains('runtime_config_test.dart')));
    });
  });
}
