/// Synor Hosting SDK Client for Flutter
///
/// Decentralized web hosting with domain management, DNS, and deployments.
library synor_hosting;

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';

export 'types.dart';

/// Synor Hosting Client
class SynorHosting {
  final HostingConfig config;
  final http.Client _client;
  bool _closed = false;

  SynorHosting(this.config) : _client = http.Client();

  // ==================== Domain Operations ====================

  Future<DomainAvailability> checkAvailability(String name) async {
    final resp = await _request('GET', '/domains/check/${Uri.encodeComponent(name)}');
    return DomainAvailability.fromJson(resp);
  }

  Future<Domain> registerDomain(String name, {RegisterDomainOptions? options}) async {
    final body = <String, dynamic>{'name': name};
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/domains', body);
    return Domain.fromJson(resp);
  }

  Future<Domain> getDomain(String name) async {
    final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}');
    return Domain.fromJson(resp);
  }

  Future<List<Domain>> listDomains() async {
    final resp = await _request('GET', '/domains');
    return (resp['domains'] as List).map((e) => Domain.fromJson(e)).toList();
  }

  Future<Domain> updateDomainRecord(String name, DomainRecord record) async {
    final resp = await _request('PUT', '/domains/${Uri.encodeComponent(name)}/record', record.toJson());
    return Domain.fromJson(resp);
  }

  Future<DomainRecord> resolveDomain(String name) async {
    final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}/resolve');
    return DomainRecord.fromJson(resp);
  }

  Future<Domain> renewDomain(String name, int years) async {
    final resp = await _request('POST', '/domains/${Uri.encodeComponent(name)}/renew', {'years': years});
    return Domain.fromJson(resp);
  }

  // ==================== DNS Operations ====================

  Future<DnsZone> getDnsZone(String domain) async {
    final resp = await _request('GET', '/dns/${Uri.encodeComponent(domain)}');
    return DnsZone.fromJson(resp);
  }

  Future<DnsZone> setDnsRecords(String domain, List<DnsRecord> records) async {
    final resp = await _request('PUT', '/dns/${Uri.encodeComponent(domain)}', {
      'records': records.map((r) => r.toJson()).toList(),
    });
    return DnsZone.fromJson(resp);
  }

  Future<DnsZone> addDnsRecord(String domain, DnsRecord record) async {
    final resp = await _request('POST', '/dns/${Uri.encodeComponent(domain)}/records', record.toJson());
    return DnsZone.fromJson(resp);
  }

  Future<DnsZone> deleteDnsRecord(String domain, String recordType, String name) async {
    final path = '/dns/${Uri.encodeComponent(domain)}/records/$recordType/${Uri.encodeComponent(name)}';
    final resp = await _request('DELETE', path);
    return DnsZone.fromJson(resp);
  }

  // ==================== Deployment Operations ====================

  Future<Deployment> deploy(String cid, {DeployOptions? options}) async {
    final body = <String, dynamic>{'cid': cid};
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/deployments', body);
    return Deployment.fromJson(resp);
  }

  Future<Deployment> getDeployment(String id) async {
    final resp = await _request('GET', '/deployments/${Uri.encodeComponent(id)}');
    return Deployment.fromJson(resp);
  }

  Future<List<Deployment>> listDeployments({String? domain}) async {
    final path = domain != null ? '/deployments?domain=${Uri.encodeComponent(domain)}' : '/deployments';
    final resp = await _request('GET', path);
    return (resp['deployments'] as List).map((e) => Deployment.fromJson(e)).toList();
  }

  Future<Deployment> rollback(String domain, String deploymentId) async {
    final resp = await _request('POST', '/deployments/${Uri.encodeComponent(deploymentId)}/rollback', {
      'domain': domain,
    });
    return Deployment.fromJson(resp);
  }

  Future<void> deleteDeployment(String id) async {
    await _request('DELETE', '/deployments/${Uri.encodeComponent(id)}');
  }

  // ==================== SSL Operations ====================

  Future<Certificate> provisionSsl(String domain, {ProvisionSslOptions? options}) async {
    final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}', options?.toJson());
    return Certificate.fromJson(resp);
  }

  Future<Certificate> getCertificate(String domain) async {
    final resp = await _request('GET', '/ssl/${Uri.encodeComponent(domain)}');
    return Certificate.fromJson(resp);
  }

  Future<Certificate> renewCertificate(String domain) async {
    final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}/renew');
    return Certificate.fromJson(resp);
  }

  Future<void> deleteCertificate(String domain) async {
    await _request('DELETE', '/ssl/${Uri.encodeComponent(domain)}');
  }

  // ==================== Site Configuration ====================

  Future<Map<String, dynamic>> getSiteConfig(String domain) async {
    return await _request('GET', '/sites/${Uri.encodeComponent(domain)}/config');
  }

  Future<Map<String, dynamic>> updateSiteConfig(String domain, Map<String, dynamic> siteConfig) async {
    return await _request('PATCH', '/sites/${Uri.encodeComponent(domain)}/config', siteConfig);
  }

  Future<int> purgeCache(String domain, {List<String>? paths}) async {
    final resp = await _request('DELETE', '/sites/${Uri.encodeComponent(domain)}/cache', {
      if (paths != null) 'paths': paths,
    });
    return resp['purged'] as int;
  }

  // ==================== Analytics ====================

  Future<AnalyticsData> getAnalytics(String domain, {AnalyticsOptions? options}) async {
    var path = '/sites/${Uri.encodeComponent(domain)}/analytics';
    final params = <String>[];
    if (options?.period != null) params.add('period=${Uri.encodeComponent(options!.period!)}');
    if (options?.start != null) params.add('start=${Uri.encodeComponent(options!.start!)}');
    if (options?.end != null) params.add('end=${Uri.encodeComponent(options!.end!)}');
    if (params.isNotEmpty) path = '$path?${params.join('&')}';

    final resp = await _request('GET', path);
    return AnalyticsData.fromJson(resp);
  }

  // ==================== Lifecycle ====================

  void close() {
    _closed = true;
    _client.close();
  }

  bool get isClosed => _closed;

  Future<bool> healthCheck() async {
    try {
      final resp = await _request('GET', '/health');
      return resp['status'] == 'healthy';
    } catch (_) {
      return false;
    }
  }

  // ==================== Private Methods ====================

  Future<Map<String, dynamic>> _request(
    String method,
    String path, [
    Map<String, dynamic>? body,
  ]) async {
    if (_closed) throw const HostingException('Client has been closed');

    Exception? lastError;
    for (var attempt = 0; attempt < config.retries; attempt++) {
      try {
        return await _doRequest(method, path, body);
      } catch (e) {
        lastError = e as Exception;
        if (attempt < config.retries - 1) {
          await Future.delayed(Duration(seconds: 1 << attempt));
        }
      }
    }
    throw lastError ?? const HostingException('Unknown error');
  }

  Future<Map<String, dynamic>> _doRequest(
    String method,
    String path,
    Map<String, dynamic>? body,
  ) async {
    final uri = Uri.parse('${config.endpoint}$path');
    final headers = {
      'Authorization': 'Bearer ${config.apiKey}',
      'Content-Type': 'application/json',
      'X-SDK-Version': 'flutter/0.1.0',
    };

    http.Response response;
    switch (method) {
      case 'GET':
        response = await _client.get(uri, headers: headers);
        break;
      case 'POST':
        response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
        break;
      case 'PUT':
        response = await _client.put(uri, headers: headers, body: jsonEncode(body));
        break;
      case 'PATCH':
        response = await _client.patch(uri, headers: headers, body: jsonEncode(body));
        break;
      case 'DELETE':
        response = await _client.delete(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
        break;
      default:
        throw HostingException('Unknown method: $method');
    }

    if (response.statusCode >= 400) {
      final error = jsonDecode(response.body) as Map<String, dynamic>;
      throw HostingException(
        error['message'] as String? ?? 'HTTP ${response.statusCode}',
        code: error['code'] as String?,
        statusCode: response.statusCode,
      );
    }

    if (response.body.isEmpty) return {};
    return jsonDecode(response.body) as Map<String, dynamic>;
  }
}
