import 'package:comunic/enums/post_kind.dart';
import 'package:comunic/enums/post_target.dart';
import 'package:comunic/enums/post_visibility_level.dart';
import 'package:comunic/enums/user_access_levels.dart';
import 'package:comunic/helpers/comments_helper.dart';
import 'package:comunic/helpers/survey_helper.dart';
import 'package:comunic/helpers/websocket_helper.dart';
import 'package:comunic/lists/comments_list.dart';
import 'package:comunic/lists/posts_list.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/displayed_content.dart';
import 'package:comunic/models/new_post.dart';
import 'package:comunic/models/post.dart';
import 'package:http_parser/http_parser.dart';

/// Posts helper
///
/// @author Pierre HUBERT

const _APIPostsVisibilityLevelMap = {
  "public": PostVisibilityLevel.PUBLIC,
  "friends": PostVisibilityLevel.FRIENDS,
  "private": PostVisibilityLevel.USER,
  "members": PostVisibilityLevel.GROUP_MEMBERS
};

const _APIPostsKindsMap = {
  "text": PostKind.TEXT,
  "image": PostKind.IMAGE,
  "weblink": PostKind.WEB_LINK,
  "pdf": PostKind.PDF,
  "countdown": PostKind.COUNTDOWN,
  "survey": PostKind.SURVEY,
  "youtube": PostKind.YOUTUBE
};

const _APIUserAccessMap = {
  "no-access": UserAccessLevels.NONE,
  "basic": UserAccessLevels.BASIC,
  "intermediate": UserAccessLevels.INTERMEDIATE,
  "full": UserAccessLevels.FULL
};

const _APIPostsTargetKindsMap = {
  PostTarget.USER_PAGE: "user",
  PostTarget.GROUP_PAGE: "group"
};

class PostsHelper {
  /// Stores the list of posts we are registered to
  ///
  /// First int = post ID
  /// Second int = number of registered people
  static final _registeredPosts = Map<int, int>();

  /// Get the list of latest posts. Return the list of posts or null in case of
  /// failure
  Future<PostsList> getLatest({int from = 0}) async {
    final response =
        await APIRequest(uri: "posts/get_latest", needLogin: true, args: {
      "include_groups": true.toString(),
      "startFrom": from.toString(),
    }).exec();

    if (response.code != 200) return null;

    try {
      // Parse & return the list of posts
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
    } catch (e) {
      print(e.toString());
      return null;
    }
  }

  /// Get the list of posts of a user
  Future<PostsList> getUserPosts(int userID, {int from = 0}) async {
    final response = await (APIRequest(uri: "posts/get_user", needLogin: true)
          ..addInt("userID", userID)
          ..addInt("startFrom", from == 0 ? 0 : from - 1))
        .exec();

    if (response.code != 200) return null;

    try {
      // Parse & return the list of posts
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
    } catch (e) {
      print(e.toString());
      return null;
    }
  }

  /// Get the list of posts of a group
  Future<PostsList> getGroupPosts(int groupID, {int from = 0}) async {
    final response = await (APIRequest(uri: "posts/get_group", needLogin: true)
          ..addInt("groupID", groupID)
          ..addInt("startFrom", from == 0 ? 0 : from - 1))
        .exec();

    if (response.code != 200) return null;

    try {
      // Parse & return the list of posts
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
    } catch (e) {
      print(e.toString());
      return null;
    }
  }

  /// Get a single post information
  Future<Post> getSingle(int postID) async {
    final response = await APIRequest(
      uri: "posts/get_single",
      args: {"postID": postID.toString()},
      needLogin: true,
    ).exec();

    if (!response.isOK)
      throw Exception("Could not get information about the post!");

    return _apiToPost(response.getObject());
  }

  /// Create a new post
  ///
  /// This function crash in case of error
  Future<void> createPost(NewPost post) async {
    APIRequest request =
        APIRequest(uri: "posts/create", needLogin: true, args: {
      "kind-page": _APIPostsTargetKindsMap[post.target],
      "kind-id": post.targetID.toString(),
      "visibility": _APIPostsVisibilityLevelMap.map(
          (s, v) => MapEntry(v, s))[post.visibility],
      "kind": _APIPostsKindsMap.map((s, k) => MapEntry(k, s))[post.kind],
      "content": post.content
    });

    switch (post.kind) {
      case PostKind.TEXT:
        break;

      case PostKind.IMAGE:
        request.addBytesFile("image", post.image);
        break;

      case PostKind.WEB_LINK:
        request.addString("url", post.url);
        break;

      case PostKind.PDF:
        request.addBytesFile(
            "pdf",
            BytesFile("file.pdf", post.pdf,
                type: MediaType.parse("application/pdf")));
        break;

      case PostKind.COUNTDOWN:
        request.addInt(
            "time-end", (post.timeEnd.millisecondsSinceEpoch / 1000).floor());
        break;

      case PostKind.SURVEY:
        request.addString("question", post.survey.question);
        request.addString("answers", post.survey.answers.join("<>"));
        request.addBool("allowNewAnswers", post.survey.allowNewChoicesCreation);
        break;

      case PostKind.YOUTUBE:
        request.addString("youtube_id", post.youtubeId);
        break;

      default:
        throw Exception("Unsupported post type :" + post.kind.toString());
        break;
    }

    final response = await request.execWithFiles();

    if (!response.isOK) throw Exception("Could not create the post !");
  }

  /// Update a post content
  Future<bool> updateContent(int id, String newContent) async {
    return (await APIRequest(
      uri: "posts/update_content",
      needLogin: true,
      args: {
        "postID": id.toString(),
        "new_content": newContent,
      },
    ).exec())
        .isOK;
  }

  /// Update a post visibility
  Future<bool> setVisibility(int id, PostVisibilityLevel level) async {
    return (await APIRequest(
      uri: "posts/set_visibility_level",
      needLogin: true,
      args: {
        "postID": id.toString(),
        "new_level":
            _APIPostsVisibilityLevelMap.map((k, v) => MapEntry(v, k))[level]
      },
    ).exec())
        .isOK;
  }

  /// Delete a post
  Future<bool> delete(int id) async {
    return (await APIRequest(
      uri: "posts/delete",
      needLogin: true,
      args: {"postID": id.toString()},
    ).exec())
        .isOK;
  }

  /// Register to a post events
  Future<void> registerPostEvents(int id) async {
    if (_registeredPosts.containsKey(id))
      _registeredPosts[id]++;
    else {
      _registeredPosts[id] = 1;
      await ws("\$main/register_post", {"postID": id});
    }
  }

  /// Un-register to post events
  Future<void> unregisterPostEvents(int id) async {
    if (!_registeredPosts.containsKey(id)) return;

    _registeredPosts[id]--;

    if (_registeredPosts[id] <= 0) {
      _registeredPosts.remove(id);
      await ws("\$main/unregister_post", {"postID": id});
    }
  }

  /// Turn an API entry into a [Post] object
  Post _apiToPost(Map<String, dynamic> map) {
    final postKind = _APIPostsKindsMap[map["kind"]];

    // Parse comments
    CommentsList comments;
    if (map["comments"] != null) {
      comments = CommentsList();
      map["comments"]
          .forEach((v) => comments.add(CommentsHelper.apiToComment(v)));
    }

    final survey = postKind == PostKind.SURVEY
        ? SurveyHelper.apiToSurvey(map["data_survey"])
        : null;

    return Post(
        id: map["ID"],
        userID: map["userID"],
        userPageID: map["user_page_id"],
        groupID: map["group_id"],
        timeSent: map["post_time"],
        content: DisplayedString(map["content"]),
        visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]],
        kind: postKind,
        fileSize: map["file_size"],
        fileType: map["file_type"],
        filePath: map["file_path"],
        fileURL: map["file_path_url"],
        timeEnd: map["time_end"],
        linkURL: map["link_url"],
        linkTitle: map["link_title"],
        linkDescription: map["link_description"],
        linkImage: map["link_image"],
        likes: map["likes"],
        userLike: map["userlike"],
        access: _APIUserAccessMap[map["user_access"]],
        comments: comments,
        survey: survey);
  }
}