package com.example.tampopo_client.viewmodels;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;

import com.example.tampopo_client.models.Activity;
import com.example.tampopo_client.resources.ActivitiesResource;
import com.example.tampopo_client.resources.UserResource;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

/**
 * アクティビティを扱うためのViewModel
 *
 * @author Shohei Yamagiwa
 * @implNote Repositoryは作成せずに、すべてViewModelで処理する
 */
public class ActivityViewModel extends RealTimeViewModel {
    private final ActivitiesResource activitiesResource;
    private final UserResource userResource;

    private final MutableLiveData<Activity> myLatestActivityLiveData; // 自分の最新のアクティビティ
    private final Map<String, MutableLiveData<List<Activity>>> friendToActivitiesLiveData; // <フレンドのユーザーID, フレンドのアクティビティのリスト>
    private final MutableLiveData<List<String>> friendUserIdsLiveData; // フレンドのユーザーIDのリスト

    private final String myUserId;
    private final String myToken;

    private final List<UserActivityStatusChangeListener> userActivityStatusChangeListeners;

    private final List<String> sortedFriendUserIds = new ArrayList<>();


    /**
     * ActivityのViewModelを作成する。
     *
     * @param myUserId 自身のユーザーID
     * @param myToken  自身の認証用トークン
     */
    public ActivityViewModel(String myUserId, String myToken) {
        this.myUserId = myUserId;
        this.myToken = myToken;

        // Retrofitの初期化
        final Retrofit retrofit = new Retrofit.Builder().baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/").addConverterFactory(JacksonConverterFactory.create()).build();
        activitiesResource = retrofit.create(ActivitiesResource.class);
        userResource = retrofit.create(UserResource.class);

        friendToActivitiesLiveData = new HashMap<>();
        friendUserIdsLiveData = new MutableLiveData<>(List.of());
        myLatestActivityLiveData = new MutableLiveData<>(null);

        userActivityStatusChangeListeners = new ArrayList<>();
    }

    @Override
    public Runnable onUpdate() {
        return () -> {
            if (friendToActivitiesLiveData == null || friendUserIdsLiveData == null || myLatestActivityLiveData == null) {
                return;
            }

            // 自分の最新のアクティビティを取得する
            pullLatestActivity(myUserId, activitiesResource, new ActivityFetchCallback() {
                @Override
                public void onSuccess(Activity activity) {
                    Activity prevActivity = myLatestActivityLiveData.getValue();

                    // アクティビティが更新されていない場合
                    LocalDateTime now = LocalDateTime.now();
                    LocalDateTime lastUpdatedTime = getDateTimeFromString(activity.getUpdateTime());

                    if (now.isAfter(lastUpdatedTime.plusSeconds(5))) {
                        for (UserActivityStatusChangeListener observer : userActivityStatusChangeListeners) {
                            // アクティビティのステータス変更をリスナーに通知する
                            if (activity.equals(prevActivity)) {
                                observer.onUserStatusChanged(myUserId, UserActivityStatusChangeListener.Status.INACTIVE);
                            } else {
                                observer.onUserStatusChanged(myUserId, UserActivityStatusChangeListener.Status.ACTIVE);
                            }
                        }
                    }

                    // 自分の新しいアクティビティをLiveDataに反映する
                    myLatestActivityLiveData.postValue(activity);
                }

                @Override
                public void onFailure(Throwable throwable) {
                    Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching my latest activity.");
                }
            });

            // 自分のフレンドの最新のアクティビティを取得して更新する
            if (friendUserIdsLiveData.isInitialized() && friendUserIdsLiveData.getValue() != null) {
                for (String userId : friendUserIdsLiveData.getValue()) {
                    pullLatestActivity(userId, activitiesResource, new ActivityFetchCallback() {
                        @Override
                        public void onSuccess(Activity activity) {
                            updateFriendToActivitiesLiveData(activity, userId);
                        }

                        @Override
                        public void onFailure(Throwable throwable) {
                            Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching friend's latest activity.", throwable);
                        }
                    });
                }
            }

            if (friendUserIdsLiveData.isInitialized()) {
                // 最新のフレンドのユーザーIDを取得して更新する
                // TODO: 適切なコールバックを使う必要あり
                pullLatestFriendUserIds(myUserId, myToken);
            }
        };
    }

    /**
     * ユーザIDからそのユーザーのアクティビティのリストのライブデータを取得し、最新のアクティビティに更新する
     *
     * @param latestActivity 最新のアクティビティ
     * @param userId         更新対象のユーザーID
     */
    private void updateFriendToActivitiesLiveData(Activity latestActivity, String userId) {
        if (friendToActivitiesLiveData.get(userId) == null) {
            friendToActivitiesLiveData.put(userId, new MutableLiveData<>(List.of()));
        }
        MutableLiveData<List<Activity>> userActivitiesLiveData = friendToActivitiesLiveData.get(userId);
        assert userActivitiesLiveData != null;

        List<Activity> userActivities = userActivitiesLiveData.getValue();

        if (userActivities == null || userActivities.isEmpty()) {
            userActivitiesLiveData.postValue(List.of(latestActivity));
        } else {
            if (userActivities.get(0).getActivityId().equals(latestActivity.getActivityId())) {
                return;
            }

            userActivitiesLiveData.postValue(List.of(latestActivity));
        }
    }

    /**
     * 自身のアクティビティを新しく作成する
     *
     * @param userId      自身のユーザーID
     * @param token       自身の認証用トークン
     * @param newActivity 新しいアクティビティのテキスト
     */
    public void createActivity(String userId, String token, String newActivity) {
        if (!myLatestActivityLiveData.isInitialized()) {
            return;
        }

        Call<String> createActivityCall = activitiesResource.addActivity(userId, token, newActivity);
        createActivityCall.enqueue(new Callback<String>() {
            @Override
            public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
                if (response.isSuccessful()) {
                    String createdActivityId = response.body();

                    Call<Activity> getActivityCall = activitiesResource.getActivity(userId, createdActivityId);
                    getActivityCall.enqueue(new Callback<Activity>() {
                        @Override
                        public void onResponse(@NonNull Call<Activity> call, @NonNull Response<Activity> response) {
                            Activity createdActivity = response.body();
                            if (createdActivity == null) {
                                return;
                            }
                            myLatestActivityLiveData.postValue(createdActivity);
                        }

                        @Override
                        public void onFailure(@NonNull Call<Activity> call, @NonNull Throwable t) {
                            Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred.", t);
                        }
                    });
                } else {
                    Log.e(ActivityViewModel.class.getSimpleName(), response.code() + " " + response.message());
                }
            }

            @Override
            public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
                Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while creating new activity.", t);
            }
        });
    }

    /**
     * 最新のユーザーのアクティビティを取得・更新する
     *
     * @param userId   取得対象のユーザーのID
     * @param resource アクティビティのリソース
     * @param callback アクティビティが取得された後に呼び出されるコールバック
     */
    private void pullLatestActivity(String userId, ActivitiesResource resource, ActivityFetchCallback callback) {
        Call<HashMap<String, Activity>> fetchActivityCall = resource.getActivities(userId, "LATEST");
        fetchActivityCall.enqueue(new Callback<HashMap<String, Activity>>() {
            @Override
            public void onResponse(@NonNull Call<HashMap<String, Activity>> call, @NonNull Response<HashMap<String, Activity>> response) {
                if (response.isSuccessful()) {
                    Collection<Activity> activities = response.body().values(); // アクティビティが存在しない場合は空のリスト
                    if (activities == null || activities.isEmpty()) {
                        return;
                    }

                    Activity latestActivity = activities.iterator().next();
                    callback.onSuccess(latestActivity);
                }
            }

            @Override
            public void onFailure(@NonNull Call<HashMap<String, Activity>> call, @NonNull Throwable t) {
                Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching the latest activity.", t);

                callback.onFailure(t);
            }
        });
    }

    /**
     * String型の日時をLocalDateTime型に変換する
     *
     * @param dateTime 変換対象の日時（文字列）
     * @return 変換後の日時（LocalDateTime）
     */
    private static LocalDateTime getDateTimeFromString(String dateTime) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
        return LocalDateTime.parse(dateTime, formatter);
    }

    /**
     * 最新の自分のフレンドのユーザーIDをすべて取得・更新する。
     *
     * @param userId 自分のユーザーID
     * @param token  自分のユーザー認証用トークン
     */
    private void pullLatestFriendUserIds(String userId, String token) {
        Call<List<String>> fetchFriendUserIdsCall = userResource.getFriends(userId, token);
        fetchFriendUserIdsCall.enqueue(new Callback<List<String>>() {
            @Override
            public void onResponse(@NonNull Call<List<String>> call, @NonNull Response<List<String>> response) {
                if (response.isSuccessful()) {

                    // MEMO: 入れ込んだだけ
                    // フレンドのIDをアクティビティ更新順に並べ替える
                    List<String> friendUserIds = friendUserIdsLiveData.getValue();
                    if (friendUserIds == null || !friendUserIds.equals(response.body())) {
                        friendUserIdsLiveData.postValue(response.body());
                        SortedSet<Friend> friends = new TreeSet<>(new Friend.UpdateTimeComparator());

                        assert friendUserIds != null;
                        friendUserIds.forEach(userId -> {
                            List<Activity> activities = getActivitiesLiveDataFromUserId(userId).getValue();
                            if (activities == null || activities.isEmpty()) {
                                return;
                            }

                            Activity latestActivity = activities.get(0);
                            friends.add(new Friend(userId, latestActivity.getUpdateTime()));
                        });

                        // 並び替えたフレンドのユーザーIDを順番に格納して更新する
                        sortedFriendUserIds.clear();
                        friends.forEach(friend -> sortedFriendUserIds.add(friend.getUserId()));
                    }
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<String>> call, @NonNull Throwable t) {
                Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching all friends' id.", t);
            }
        });
    }

    public MutableLiveData<Activity> getMyLatestActivityLiveData() {
        return myLatestActivityLiveData;
    }

    /**
     * ユーザIDからそのユーザーのアクティビティのリストのライブデータを取得する
     *
     * @param userId 取得対象のユーザーID
     * @return 取得対象のユーザーのアクティビティのリストのライブデータ
     */
    public MutableLiveData<List<Activity>> getActivitiesLiveDataFromUserId(String userId) {
        if (!friendToActivitiesLiveData.containsKey(userId)) {
            friendToActivitiesLiveData.put(userId, new MutableLiveData<>());
        }
        return friendToActivitiesLiveData.get(userId);
    }

    public MutableLiveData<List<String>> getFriendUserIdsLiveData() {
        return friendUserIdsLiveData;
    }

    public String getMyUserId() {
        return myUserId;
    }

    public String getMyToken() {
        return myToken;
    }

    public List<String> getSortedFriendUserIds() {
        return sortedFriendUserIds;
    }

    public void addActivityStatusChangeObserver(UserActivityStatusChangeListener observer) {
        userActivityStatusChangeListeners.add(observer);
    }

    public void clearActivityStatusChangeObservers() {
        userActivityStatusChangeListeners.clear();
    }

    private static class Friend {
        private final String userId;
        private final LocalDateTime latestUpdateTime;

        public Friend(String userId, String latestUpdateTime) {
            this.userId = userId;
            this.latestUpdateTime = getDateTimeFromString(latestUpdateTime);
        }

        public String getUserId() {
            return userId;
        }

        public LocalDateTime getLatestUpdateTime() {
            return latestUpdateTime;
        }

        public static class UpdateTimeComparator implements Comparator<Friend> {
            @Override
            public int compare(Friend o1, Friend o2) {
                return o1.getLatestUpdateTime().compareTo(o2.getLatestUpdateTime());
            }
        }
    }

    private interface ActivityFetchCallback {
        void onSuccess(Activity activity);

        void onFailure(Throwable throwable);
    }
}
