import 'dart:async';
import 'dart:convert';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;

import '../../../core/config/app_environment_provider.dart';
import '../../../core/network/api_error.dart';
import '../../../core/network/http_client_provider.dart';
import '../../auth/data/auth_session_store.dart';
import '../domain/organization_models.dart';
import 'org_context_api_client.dart';

abstract class OrganizationRepository {
  const OrganizationRepository();

  Future<TenantListResponse> listTenants();

  Future<OrgChartSnapshot> getOrgChart({
    String? tenantId,
    bool refresh = false,
  });
}

class OrganizationApiClient {
  const OrganizationApiClient({
    required this.httpClient,
    required this.baseUri,
    required this.sessionStore,
    this.timeout = const Duration(seconds: 10),
  });

  final http.Client httpClient;
  final Uri baseUri;
  final AuthSessionStore sessionStore;
  final Duration timeout;

  Future<TenantListResponse> listTenants() async {
    final response = await httpClient
        .get(
          _resolve('/api/v1/tdc114plus/organization/tenants'),
          headers: await _headers(),
        )
        .timeout(timeout);
    return TenantListResponse.fromJson(_decodeOk(response));
  }

  Future<OrgChartSnapshot> getOrgChart({
    String? tenantId,
    bool refresh = false,
  }) async {
    final response = await httpClient
        .get(
          _resolve(
            '/api/v1/tdc114plus/organization/orgchart',
            queryParameters: {
              'tenantId': tenantId,
              if (refresh) 'refresh': 'true',
            },
          ),
          headers: await _headers(),
        )
        .timeout(timeout);
    return OrgChartSnapshot.fromJson(_decodeOk(response));
  }

  Future<Map<String, String>> _headers() async {
    final session = await sessionStore.load();
    return {
      'accept': 'application/json',
      if (session?.token != null) 'authorization': 'Bearer ${session!.token}',
    };
  }

  Map<String, dynamic> _decodeOk(http.Response response) {
    final decoded = _decodeObject(response.body);
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw ApiException(
        statusCode: response.statusCode,
        apiError: ApiError.fromJson(decoded),
      );
    }
    return decoded;
  }

  Uri _resolve(String path, {Map<String, String?> queryParameters = const {}}) {
    final normalizedBase = baseUri.path.endsWith('/')
        ? baseUri
        : baseUri.replace(path: '${baseUri.path}/');
    final uri = normalizedBase.resolve(path.replaceFirst(RegExp(r'^/'), ''));
    final filteredQuery = Map<String, String>.fromEntries(
      queryParameters.entries
          .where((entry) {
            return entry.value != null && entry.value!.trim().isNotEmpty;
          })
          .map((entry) => MapEntry(entry.key, entry.value!)),
    );
    return uri.replace(queryParameters: filteredQuery);
  }

  Map<String, dynamic> _decodeObject(String body) {
    final decoded = jsonDecode(body);
    if (decoded is Map<String, dynamic>) {
      return decoded;
    }
    throw const FormatException('Expected JSON object response');
  }
}

class RemoteOrganizationRepository implements OrganizationRepository {
  const RemoteOrganizationRepository({required this.apiClient});

  final OrganizationApiClient apiClient;

  @override
  Future<TenantListResponse> listTenants() => apiClient.listTenants();

  @override
  Future<OrgChartSnapshot> getOrgChart({
    String? tenantId,
    bool refresh = false,
  }) {
    return apiClient.getOrgChart(tenantId: tenantId, refresh: refresh);
  }
}

class OrgContextOrganizationRepository implements OrganizationRepository {
  const OrgContextOrganizationRepository({required this.orgContextApiClient});

  final OrgContextApiClient orgContextApiClient;

  @override
  Future<TenantListResponse> listTenants() async {
    final snapshot = await orgContextApiClient.fetchOrgContext();
    return TenantListResponse(
      items: snapshot.tenants,
      generatedAt: snapshot.generatedAt,
    );
  }

  @override
  Future<OrgChartSnapshot> getOrgChart({
    String? tenantId,
    bool refresh = false,
  }) async {
    final snapshot = await orgContextApiClient.fetchOrgContext();
    if (tenantId == null || tenantId.trim().isEmpty) {
      return snapshot;
    }

    return OrgChartSnapshot(
      tenants: snapshot.tenants
          .where(
            (tenant) => tenant.id == tenantId || tenant.parentId == tenantId,
          )
          .toList(),
      employees: snapshot.employees
          .where((employee) => employee.tenantId == tenantId)
          .toList(),
      generatedAt: snapshot.generatedAt,
      cache: snapshot.cache,
    );
  }
}

final organizationApiClientProvider = Provider<OrganizationApiClient>((ref) {
  final environment = ref.watch(appEnvironmentProvider);
  return OrganizationApiClient(
    httpClient: ref.watch(httpClientProvider),
    baseUri: Uri.parse(environment.organizationApiBaseUrl),
    sessionStore: ref.watch(authSessionStoreProvider),
  );
});

final organizationRepositoryProvider = Provider<OrganizationRepository>((ref) {
  return OrgContextOrganizationRepository(
    orgContextApiClient: ref.watch(orgContextApiClientProvider),
  );
});

final tenantListProvider = FutureProvider.autoDispose<List<TenantSummary>>((
  ref,
) async {
  final response = await ref
      .watch(organizationRepositoryProvider)
      .listTenants();
  return response.items;
});

final orgChartProvider = FutureProvider.autoDispose
    .family<OrgChartSnapshot, String?>((ref, tenantId) async {
      return ref
          .watch(organizationRepositoryProvider)
          .getOrgChart(tenantId: tenantId);
    });
