import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';

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

import '../../../model/account_data.dart';
import '../../../model/endpoint_data.dart';
import '../../errors/parse_exception.dart';
import '../../http_errors.dart';
import '../nextcloud_ocs.dart';

/// Mix of two Nextcloud surfaces:
/// - User info comes from the OCS provisioning API
///   (`/ocs/v2.php/cloud/users/{userId}`).
/// - The own-avatar upload/delete uses the *core* AvatarController at
///   `/avatar/` — the OCS provisioning route has no POST (it answers 405).
///   This is the same controller the read path (`/avatar/{id}/{size}` in
///   [UserAvatar]) already talks to. CSRF is bypassed because we use Basic
///   Auth without a session cookie.

/// Core AvatarController endpoint for the logged-in user (POST sets, DELETE
/// removes). Built against the bare Nextcloud base (domain + optional path),
/// not the OCS wrapper.
Uri _coreAvatarUri() {
  final endpoint = EndpointData().nextcloud();
  return Uri.https(endpoint.domain, '${endpoint.path}/avatar/');
}

Uri _userInfoUri() =>
    NextcloudOcs.uri('cloud/users/${AccountData().getUsername()}');

Future<http.Response> _send(
  Future<http.Response> Function(Uri uri, Map<String, String> headers)
  perform,
  Uri uri,
) async {
  final headers = NextcloudOcs.headers();

  final response = (await sendGuarded(
    'Cloud $uri',
    () => perform(uri, headers),
  ))!;

  final status = response.statusCode;
  if (status >= 200 && status < 300) return response;

  final detail = httpErrorDetail('Cloud $uri', response.body, status);
  log(detail);
  throwForStatus(status, detail);
}

class SetUserAvatar {
  final Uint8List bytes;
  final String filename;
  SetUserAvatar(this.bytes, {this.filename = 'avatar.jpg'});

  Future<void> run() async {
    await _send((uri, headers) async {
      // Core AvatarController reads $_FILES['files']['error'][0] — the field
      // must be `files[]` so PHP exposes it as an array, matching the web UI.
      final req = http.MultipartRequest('POST', uri)
        ..headers.addAll(headers)
        ..files.add(
          http.MultipartFile.fromBytes('files[]', bytes, filename: filename),
        );
      final streamed = await req.send();
      return http.Response.fromStream(streamed);
    }, _coreAvatarUri());
  }
}

class DeleteUserAvatar {
  Future<void> run() async {
    await _send(
      (uri, headers) => http.delete(uri, headers: headers),
      _coreAvatarUri(),
    );
  }
}

class CloudUserInfo {
  final String userId;
  final String displayName;
  const CloudUserInfo({required this.userId, required this.displayName});
}

/// Reads the current user's provisioning record. The OCS wrapper looks like:
/// `{ "ocs": { "meta": {...}, "data": { "id": "...", "displayname": "...", ... } } }`.
/// We only need displayname; everything else is discarded.
class GetUserInfo {
  Future<CloudUserInfo> run() async {
    final uri = _userInfoUri();
    final response = await _send(
      (u, headers) => http.get(u, headers: headers),
      uri,
    );
    try {
      final root = jsonDecode(response.body) as Map<String, dynamic>;
      final data =
          (root['ocs'] as Map<String, dynamic>)['data']
              as Map<String, dynamic>;
      return CloudUserInfo(
        userId: data['id'] as String,
        displayName: (data['displayname'] as String?) ?? '',
      );
    } catch (e) {
      throw ParseException(
        technicalDetails: 'Cloud $uri user info parse: $e',
      );
    }
  }
}
