import 'dart:io';

import 'runtime_models.dart';

class RuntimeBootstrapConfig {
  const RuntimeBootstrapConfig({
    required this.workspacePath,
    required this.remoteProjectRoot,
    required this.cliPath,
    required this.remoteGateway,
  });

  final String? workspacePath;
  final String? remoteProjectRoot;
  final String? cliPath;
  final GatewayBootstrapTarget? remoteGateway;

  static Future<RuntimeBootstrapConfig> load({
    String? workspacePathHint,
    String? cliPathHint,
  }) async {
    final workspaceRoot = _resolveWorkspaceRoot(workspacePathHint);
    final openClawRoot = _resolveOpenClawRoot(
      workspaceRoot,
      cliPathHint: cliPathHint,
    );
    final env = _loadEnvFile(
      workspacePathHint: workspacePathHint,
      cliPathHint: cliPathHint,
      workspaceRoot: workspaceRoot,
      openClawRoot: openClawRoot,
    );
    return RuntimeBootstrapConfig(
      workspacePath: workspaceRoot?.path,
      remoteProjectRoot: workspaceRoot?.path,
      cliPath: _resolveCliPath(openClawRoot),
      remoteGateway: GatewayBootstrapTarget.tryParse(
        env['remote'],
        token: env['remote-token'],
      ),
    );
  }

  SettingsSnapshot mergeIntoSettings(SettingsSnapshot snapshot) {
    var next = snapshot;

    final resolvedWorkspacePath = workspacePath?.trim() ?? '';
    final resolvedRemoteProjectRoot = remoteProjectRoot?.trim() ?? '';
    final replaceWorkspacePath =
        _isDefaultWorkspacePath(snapshot.workspacePath) ||
        _isMissingTransientWorkspacePath(snapshot.workspacePath);
    final replaceRemoteProjectRoot =
        _isDefaultRemoteRoot(snapshot.remoteProjectRoot) ||
        _isMissingTransientWorkspacePath(snapshot.remoteProjectRoot);

    if (replaceWorkspacePath) {
      next = next.copyWith(
        workspacePath: resolvedWorkspacePath.isNotEmpty
            ? resolvedWorkspacePath
            : SettingsSnapshot.defaults().workspacePath,
      );
    }
    if (replaceRemoteProjectRoot) {
      next = next.copyWith(
        remoteProjectRoot: resolvedRemoteProjectRoot.isNotEmpty
            ? resolvedRemoteProjectRoot
            : (resolvedWorkspacePath.isNotEmpty
                  ? resolvedWorkspacePath
                  : SettingsSnapshot.defaults().remoteProjectRoot),
      );
    }
    if (_isDefaultCliPath(snapshot.cliPath) &&
        cliPath != null &&
        cliPath!.trim().isNotEmpty) {
      next = next.copyWith(cliPath: cliPath);
    }

    return next;
  }

  GatewayBootstrapTarget? preferredGatewayFor(RuntimeConnectionMode mode) {
    return switch (mode) {
      RuntimeConnectionMode.remote => remoteGateway,
      RuntimeConnectionMode.unconfigured => remoteGateway,
    };
  }

  static bool _isDefaultWorkspacePath(String value) =>
      value.trim().isEmpty || value.trim() == '/opt/data';

  static bool _isDefaultRemoteRoot(String value) =>
      value.trim().isEmpty ||
      value.trim() == '/opt/data/workspace' ||
      value.trim() == '/opt/data';

  static bool _isDefaultCliPath(String value) =>
      value.trim().isEmpty || value.trim() == 'openclaw';

  static bool _isMissingTransientWorkspacePath(String value) {
    final trimmed = value.trim();
    if (trimmed.isEmpty) {
      return false;
    }
    if (_isLikelyTransientPath(trimmed) &&
        FileSystemEntity.typeSync(trimmed) == FileSystemEntityType.notFound) {
      return true;
    }
    return false;
  }

  static bool _isLikelyTransientPath(String path) {
    final normalized = path.trim();
    if (normalized.isEmpty) {
      return false;
    }
    final systemTemp = Directory.systemTemp.path;
    if (normalized == systemTemp || normalized.startsWith('$systemTemp/')) {
      return true;
    }
    if (normalized.startsWith('/tmp/') ||
        normalized.startsWith('/private/tmp/')) {
      return true;
    }
    return false;
  }
}

class GatewayBootstrapTarget {
  const GatewayBootstrapTarget({
    required this.mode,
    required this.url,
    required this.host,
    required this.port,
    required this.tls,
    required this.token,
  });

  final RuntimeConnectionMode mode;
  final String url;
  final String host;
  final int port;
  final bool tls;
  final String token;

  static GatewayBootstrapTarget? tryParse(String? raw, {String? token}) {
    final trimmed = raw?.trim() ?? '';
    if (trimmed.isEmpty) {
      return null;
    }
    final uri = Uri.tryParse(trimmed);
    if (uri == null || !uri.hasScheme || (uri.host).trim().isEmpty) {
      return null;
    }
    final scheme = uri.scheme.toLowerCase();
    final tls = scheme == 'wss' || scheme == 'https';
    final port = uri.hasPort ? uri.port : (tls ? 443 : 18789);
    final host = uri.host.trim();
    return GatewayBootstrapTarget(
      mode: RuntimeConnectionMode.remote,
      url: trimmed,
      host: host,
      port: port,
      tls: tls,
      token: token?.trim() ?? '',
    );
  }
}

Map<String, String> _loadEnvFile({
  String? workspacePathHint,
  String? cliPathHint,
  Directory? workspaceRoot,
  Directory? openClawRoot,
}) {
  final candidateDirectories = <Directory>{
    Directory.current,
    ..._ancestorDirectories(Directory.current),
    ..._pathCandidates(workspacePathHint),
    ..._pathCandidates(
      cliPathHint == null ? null : File(cliPathHint).parent.path,
    ),
    ...?workspaceRoot == null ? null : <Directory>[workspaceRoot],
    ...?workspaceRoot == null ? null : _ancestorDirectories(workspaceRoot),
    ...?openClawRoot == null ? null : <Directory>[openClawRoot],
    ...?openClawRoot == null ? null : _ancestorDirectories(openClawRoot),
  };
  final candidates = candidateDirectories
      .map((directory) => File('${directory.path}/.env'))
      .toList(growable: false);

  for (final file in candidates) {
    if (!file.existsSync()) {
      continue;
    }
    final values = <String, String>{};
    for (final line in file.readAsLinesSync()) {
      final trimmed = line.trim();
      if (trimmed.isEmpty || trimmed.startsWith('#')) {
        continue;
      }
      final separator = trimmed.indexOf(':');
      if (separator <= 0) {
        continue;
      }
      final key = trimmed.substring(0, separator).trim();
      final value = trimmed.substring(separator + 1).trim();
      if (key.isNotEmpty && value.isNotEmpty) {
        values[key] = value;
      }
    }
    if (values.isNotEmpty) {
      return values;
    }
  }
  return const <String, String>{};
}

Directory? _resolveWorkspaceRoot(String? workspacePathHint) {
  final candidates = <Directory>{
    ..._pathCandidates(workspacePathHint),
    Directory.current,
    ..._ancestorDirectories(Directory.current),
  }.toList(growable: false);
  for (final candidate in candidates) {
    if (File('${candidate.path}/pubspec.yaml').existsSync() &&
        File('${candidate.path}/lib/main.dart').existsSync()) {
      return candidate;
    }
  }
  return null;
}

Directory? _resolveOpenClawRoot(
  Directory? workspaceRoot, {
  String? cliPathHint,
}) {
  final cliFile = cliPathHint == null ? null : File(cliPathHint);
  if (cliFile != null && cliFile.existsSync()) {
    final cliParent = cliFile.parent;
    if (File('${cliParent.path}/openclaw.mjs').existsSync()) {
      return cliParent;
    }
  }
  if (workspaceRoot == null) {
    return null;
  }
  final sibling = Directory('${workspaceRoot.parent.path}/openclaw.svc.plus');
  if (File('${sibling.path}/openclaw.mjs').existsSync()) {
    return sibling;
  }
  return null;
}

String? _resolveCliPath(Directory? openClawRoot) {
  if (openClawRoot == null) {
    return null;
  }
  final candidate = File('${openClawRoot.path}/openclaw.mjs');
  if (!candidate.existsSync()) {
    return null;
  }
  return candidate.path;
}

List<Directory> _ancestorDirectories(Directory start) {
  final ancestors = <Directory>[];
  var current = start.absolute;
  while (true) {
    final parent = current.parent;
    if (parent.path == current.path) {
      break;
    }
    ancestors.add(parent);
    current = parent;
  }
  return ancestors;
}

List<Directory> _pathCandidates(String? rawPath) {
  final trimmed = rawPath?.trim() ?? '';
  if (trimmed.isEmpty) {
    return const <Directory>[];
  }
  final fileSystemEntityType = FileSystemEntity.typeSync(trimmed);
  final directory = switch (fileSystemEntityType) {
    FileSystemEntityType.directory => Directory(trimmed),
    FileSystemEntityType.file => File(trimmed).parent,
    _ => Directory(trimmed),
  };
  if (!directory.existsSync()) {
    return const <Directory>[];
  }
  return <Directory>[directory, ..._ancestorDirectories(directory)];
}
