import 'dart:io';
import 'package:yaml/yaml.dart';

class AgentConfigException implements Exception {
  final String message;
  AgentConfigException(this.message);

  @override
  String toString() => message;
}

class AgentIdentityConfig {
  final String id;
  final String? alias;
  final String enrollmentToken;

  const AgentIdentityConfig({
    required this.id,
    this.alias,
    required this.enrollmentToken,
  });
}

class EdgeConnectionConfig {
  final String url;

  const EdgeConnectionConfig({
    required this.url,
  });
}

class AgentRuntimeConfig {
  final String installDir;
  final String workspaceRoot;
  final String logDir;

  const AgentRuntimeConfig({
    required this.installDir,
    required this.workspaceRoot,
    required this.logDir,
  });
}

class AgentConfig {
  final AgentIdentityConfig agent;
  final EdgeConnectionConfig edge;
  final AgentRuntimeConfig runtime;

  const AgentConfig({
    required this.agent,
    required this.edge,
    required this.runtime,
  });

  static String _requiredString(Map map, String sectionName, String key) {
    if (!map.containsKey(key)) {
      throw AgentConfigException(
          'Missing or empty required field "$sectionName.$key"');
    }
    final val = map[key];
    if (val is! String) {
      throw AgentConfigException(
          'Invalid type for required field "$sectionName.$key": expected String but got ${val.runtimeType}');
    }
    if (val.trim().isEmpty) {
      throw AgentConfigException(
          'Missing or empty required field "$sectionName.$key"');
    }
    return val;
  }

  static String? _optionalString(Map map, String sectionName, String key) {
    if (!map.containsKey(key)) {
      return null;
    }
    final val = map[key];
    if (val == null) {
      return null;
    }
    if (val is! String) {
      throw AgentConfigException(
          'Invalid type for optional field "$sectionName.$key": expected String but got ${val.runtimeType}');
    }
    return val;
  }

  factory AgentConfig.fromFile(String filePath) {
    final file = File(filePath);
    if (!file.existsSync()) {
      throw AgentConfigException(
          'Agent configuration file does not exist at path: "$filePath"');
    }
    try {
      final content = file.readAsStringSync();
      return AgentConfig.fromYamlContent(content);
    } on AgentConfigException {
      rethrow;
    } catch (e) {
      throw AgentConfigException('Failed to read agent configuration file: $e');
    }
  }

  factory AgentConfig.fromYamlContent(String content) {
    dynamic root;
    try {
      root = loadYaml(content);
    } catch (e) {
      throw AgentConfigException('Failed to parse YAML content: $e');
    }

    if (root is! Map) {
      throw AgentConfigException('Agent configuration root must be a map');
    }

    final agentMap = root['agent'];
    if (agentMap is! Map) {
      throw AgentConfigException('Missing or invalid "agent" section');
    }

    final agentId = _requiredString(agentMap, 'agent', 'id');
    final enrollmentToken =
        _requiredString(agentMap, 'agent', 'enrollment_token');
    final alias = _optionalString(agentMap, 'agent', 'alias');

    final edgeMap = root['edge'];
    if (edgeMap is! Map) {
      throw AgentConfigException('Missing or invalid "edge" section');
    }

    final edgeUrl = _requiredString(edgeMap, 'edge', 'url');

    final runtimeMap = root['runtime'];
    if (runtimeMap is! Map) {
      throw AgentConfigException('Missing or invalid "runtime" section');
    }

    final installDir = _requiredString(runtimeMap, 'runtime', 'install_dir');
    final workspaceRoot =
        _requiredString(runtimeMap, 'runtime', 'workspace_root');
    final logDir = _requiredString(runtimeMap, 'runtime', 'log_dir');

    return AgentConfig(
      agent: AgentIdentityConfig(
        id: agentId,
        alias: alias,
        enrollmentToken: enrollmentToken,
      ),
      edge: EdgeConnectionConfig(
        url: edgeUrl,
      ),
      runtime: AgentRuntimeConfig(
        installDir: installDir,
        workspaceRoot: workspaceRoot,
        logDir: logDir,
      ),
    );
  }
}
