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

import 'platform_environment.dart';

/// Bridge for generating Codex configuration files.
///
/// This class generates `~/.codex/config.toml` and `~/.codex/auth.json`
/// to configure Codex CLI to use XWorkmate's AI Gateway.
class CodexConfigBridge {
  static const String managedBlockStartInternal =
      '# BEGIN XWORKMATE MANAGED BLOCK';
  static const String managedBlockEndInternal = '# END XWORKMATE MANAGED BLOCK';
  static const String managedMcpBlockStartInternal =
      '# BEGIN XWORKMATE MANAGED MCP BLOCK';
  static const String managedMcpBlockEndInternal =
      '# END XWORKMATE MANAGED MCP BLOCK';

  final String codexHome;

  CodexConfigBridge({String? codexHome})
    : codexHome = codexHome ?? resolveCodexHomeDirectory();

  /// Generate config.toml to use XWorkmate AI Gateway.
  Future<void> configureForGateway({
    required String gatewayUrl,
    required String apiKey,
    String providerName = 'xworkmate',
    String defaultModel = 'gpt-4.1',
    CodexSandboxMode sandbox = CodexSandboxMode.workspaceWrite,
    CodexApprovalPolicy approval = CodexApprovalPolicy.suggest,
    Map<String, String>? extraConfig,
  }) async {
    final configDir = Directory(codexHome);
    if (!await configDir.exists()) {
      await configDir.create(recursive: true);
    }

    final configFile = File('$codexHome/config.toml');
    final existingConfig = await configFile.exists()
        ? await configFile.readAsString()
        : '';
    final preserved = stripManagedBlockInternal(existingConfig).trimRight();
    final managedBlock = buildManagedBlockInternal(
      gatewayUrl: gatewayUrl,
      apiKey: apiKey,
      providerName: providerName,
      defaultModel: defaultModel,
      sandbox: sandbox,
      approval: approval,
      extraConfig: extraConfig,
    );
    final merged = preserved.isEmpty
        ? '$managedBlock\n'
        : '$preserved\n\n$managedBlock\n';
    await configFile.writeAsString(merged);
  }

  String buildProviderSectionInternal({
    required String providerName,
    required String gatewayUrl,
    required String apiKey,
  }) {
    final buffer = StringBuffer();
    buffer.writeln('[model_providers.$providerName]');
    buffer.writeln('name = "XWorkmate AI Gateway"');
    buffer.writeln('base_url = "$gatewayUrl"');

    // Use experimental_bearer_token for API key
    if (apiKey.isNotEmpty) {
      buffer.writeln('experimental_bearer_token = "$apiKey"');
    }

    buffer.writeln('wire_api = "responses"');
    buffer.writeln('supports_websockets = false');

    return buffer.toString();
  }

  String buildManagedBlockInternal({
    required String gatewayUrl,
    required String apiKey,
    required String providerName,
    required String defaultModel,
    required CodexSandboxMode sandbox,
    required CodexApprovalPolicy approval,
    Map<String, String>? extraConfig,
  }) {
    final providerSection = buildProviderSectionInternal(
      providerName: providerName,
      gatewayUrl: gatewayUrl,
      apiKey: apiKey,
    );
    final config = StringBuffer();
    config.writeln(managedBlockStartInternal);
    config.writeln('# Generated by XWorkmate - AI Gateway Configuration');
    config.writeln('# Last updated: ${DateTime.now().toIso8601String()}');
    config.writeln();
    config.writeln(providerSection);
    config.writeln();
    config.writeln('[model]');
    config.writeln('model = "$defaultModel"');
    config.writeln();
    config.writeln('[approval_policy]');
    config.writeln('policy = "${approval.value}"');
    config.writeln();
    config.writeln('[sandbox]');
    config.writeln('mode = "${sandbox.value}"');
    config.writeln();
    config.writeln('[features]');
    config.writeln('child_agents_md = true');
    config.writeln('realtime = false');
    config.writeln();
    if (extraConfig != null && extraConfig.isNotEmpty) {
      config.writeln('# Custom configuration');
      for (final entry in extraConfig.entries) {
        config.writeln('${entry.key} = "${entry.value}"');
      }
      config.writeln();
    }
    config.writeln(managedBlockEndInternal);
    return config.toString().trimRight();
  }

  String stripManagedBlockInternal(String content) {
    return stripBlockInternal(
      content,
      managedBlockStartInternal,
      managedBlockEndInternal,
    );
  }

  String stripManagedMcpBlockInternal(String content) {
    return stripBlockInternal(
      content,
      managedMcpBlockStartInternal,
      managedMcpBlockEndInternal,
    );
  }

  String stripBlockInternal(
    String content,
    String startMarker,
    String endMarker,
  ) {
    if (content.isEmpty) {
      return content;
    }

    var remaining = content;
    while (true) {
      final start = remaining.indexOf(startMarker);
      if (start < 0) {
        break;
      }
      final end = remaining.indexOf(endMarker, start);
      if (end < 0) {
        remaining = remaining.substring(0, start);
        break;
      }
      remaining =
          remaining.substring(0, start) +
          remaining.substring(end + endMarker.length);
    }
    return remaining;
  }

  /// Generate auth.json for ChatGPT OAuth authentication.
  Future<void> configureAuth({
    required String accessToken,
    String? refreshToken,
    DateTime? expiresAt,
    String? email,
    String? plan,
  }) async {
    final authFile = File('$codexHome/auth.json');

    final auth = <String, dynamic>{
      'access_token': accessToken,
      'last_refresh': DateTime.now().toIso8601String(),
    };

    if (refreshToken != null && refreshToken.isNotEmpty) {
      auth['refresh_token'] = refreshToken;
    }

    if (expiresAt != null) {
      auth['expires_at'] = expiresAt.millisecondsSinceEpoch;
    }

    if (email != null && email.isNotEmpty) {
      auth['email'] = email;
    }

    if (plan != null && plan.isNotEmpty) {
      auth['plan'] = plan;
    }

    await authFile.writeAsString(JsonEncoder.withIndent('  ').convert(auth));
  }

  /// Configure MCP servers for Codex.
  Future<void> configureMcpServers({
    required List<CodexMcpServer> servers,
    bool append = true,
  }) async {
    final configFile = File('$codexHome/config.toml');

    String existingConfig = '';
    if (await configFile.exists()) {
      existingConfig = await configFile.readAsString();
    }

    final buffer = StringBuffer();

    if (append && existingConfig.isNotEmpty) {
      buffer.writeln(existingConfig);
      buffer.writeln();
    }

    buffer.writeln('# MCP Servers');

    for (final server in servers) {
      buffer.writeln('[mcp_servers.${server.name}]');
      buffer.writeln('command = "${server.command}"');

      if (server.args.isNotEmpty) {
        buffer.writeln('args = ${formatTomlArrayInternal(server.args)}');
      }

      if (server.env.isNotEmpty) {
        buffer.writeln('[mcp_servers.${server.name}.env]');
        for (final entry in server.env.entries) {
          buffer.writeln('${entry.key} = "${entry.value}"');
        }
      }

      buffer.writeln();
    }

    await configFile.writeAsString(buffer.toString());
  }

  Future<void> configureManagedMcpServers({
    required List<CodexMcpServer> servers,
  }) async {
    final configDir = Directory(codexHome);
    if (!await configDir.exists()) {
      await configDir.create(recursive: true);
    }

    final configFile = File('$codexHome/config.toml');
    final existingConfig = await configFile.exists()
        ? await configFile.readAsString()
        : '';
    final preserved = stripManagedMcpBlockInternal(existingConfig).trimRight();
    final managedBlock = buildManagedMcpBlockInternal(servers);
    final merged = preserved.isEmpty
        ? '$managedBlock\n'
        : '$preserved\n\n$managedBlock\n';
    await configFile.writeAsString(merged);
  }

  String buildManagedMcpBlockInternal(List<CodexMcpServer> servers) {
    final buffer = StringBuffer()
      ..writeln(managedMcpBlockStartInternal)
      ..writeln('# Generated by XWorkmate - Managed MCP Server Configuration')
      ..writeln('# Last updated: ${DateTime.now().toIso8601String()}')
      ..writeln();

    for (final server in servers) {
      buffer.writeln('[mcp_servers.${server.name}]');
      buffer.writeln('command = "${server.command}"');

      if (server.args.isNotEmpty) {
        buffer.writeln('args = ${formatTomlArrayInternal(server.args)}');
      }

      if (server.env.isNotEmpty) {
        buffer.writeln('[mcp_servers.${server.name}.env]');
        for (final entry in server.env.entries) {
          buffer.writeln('${entry.key} = "${entry.value}"');
        }
      }

      buffer.writeln();
    }

    buffer.writeln(managedMcpBlockEndInternal);
    return buffer.toString().trimRight();
  }

  String formatTomlArrayInternal(List<String> items) {
    if (items.isEmpty) return '[]';
    if (items.length == 1) return '["${items[0]}"]';
    return '[${items.map((s) => '"$s"').join(', ')}]';
  }

  /// Generate configuration for OpenClaw Gateway integration.
  Future<void> configureOpenClawGateway({
    required String gatewayUrl,
    required String token,
    String providerName = 'openclaw',
  }) async {
    await configureForGateway(
      gatewayUrl: gatewayUrl,
      apiKey: token,
      providerName: providerName,
    );

    // Add MCP server for OpenClaw
    await configureMcpServers(
      servers: [
        CodexMcpServer(
          name: 'openclaw',
          command: 'openclaw-mcp',
          args: ['--gateway', gatewayUrl],
          env: {'OPENCLAW_TOKEN': token},
        ),
      ],
      append: true,
    );
  }

  /// Check if Codex configuration exists.
  Future<bool> hasConfig() async {
    final configFile = File('$codexHome/config.toml');
    return configFile.exists();
  }

  /// Check if auth.json exists.
  Future<bool> hasAuth() async {
    final authFile = File('$codexHome/auth.json');
    return authFile.exists();
  }

  /// Read current model provider configuration.
  Future<Map<String, dynamic>?> readProviderConfig(String providerName) async {
    final configFile = File('$codexHome/config.toml');
    if (!await configFile.exists()) {
      return null;
    }

    final content = await configFile.readAsString();
    return parseTomlSectionInternal(content, 'model_providers.$providerName');
  }

  /// Parse a TOML section into a Map.
  Map<String, dynamic>? parseTomlSectionInternal(
    String content,
    String section,
  ) {
    final lines = content.split('\n');
    final result = <String, dynamic>{};
    bool inSection = false;

    for (final line in lines) {
      final trimmed = line.trim();

      if (trimmed.isEmpty || trimmed.startsWith('#')) continue;

      if (trimmed.startsWith('[')) {
        final sectionName = trimmed.substring(1, trimmed.length - 1);
        inSection = sectionName == section;
        continue;
      }

      if (inSection) {
        final eqIndex = trimmed.indexOf('=');
        if (eqIndex > 0) {
          final key = trimmed.substring(0, eqIndex).trim();
          var value = trimmed.substring(eqIndex + 1).trim();

          // Remove quotes
          if ((value.startsWith('"') && value.endsWith('"')) ||
              (value.startsWith("'") && value.endsWith("'"))) {
            value = value.substring(1, value.length - 1);
          }

          result[key] = value;
        }
      }
    }

    return result.isNotEmpty ? result : null;
  }

  /// Clear all Codex configuration.
  Future<void> clearConfig() async {
    final configDir = Directory(codexHome);
    if (await configDir.exists()) {
      await configDir.delete(recursive: true);
    }
  }
}

/// Codex sandbox mode for configuration.
enum CodexSandboxMode {
  readOnly('read-only'),
  workspaceWrite('workspace-write'),
  dangerFullAccess('danger-full-access');

  final String value;
  const CodexSandboxMode(this.value);
}

/// Codex approval policy for configuration.
enum CodexApprovalPolicy {
  suggest('suggest'),
  autoEdit('auto-edit'),
  fullAuto('full-auto');

  final String value;
  const CodexApprovalPolicy(this.value);
}

/// MCP server configuration for Codex.
class CodexMcpServer {
  final String name;
  final String command;
  final List<String> args;
  final Map<String, String> env;

  const CodexMcpServer({
    required this.name,
    required this.command,
    this.args = const [],
    this.env = const {},
  });
}
