/*
 * Copyright (C) 2000 - 2025 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.mobile.client.common;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.*;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.googlecode.gwt.crypto.bouncycastle.InvalidCipherTextException;
import com.googlecode.gwt.crypto.client.TripleDesCipher;
import org.silverpeas.mobile.client.SpMobil;
import org.silverpeas.mobile.client.common.event.ErrorEvent;
import org.silverpeas.mobile.client.common.event.authentication.AuthenticationErrorEvent;
import org.silverpeas.mobile.client.common.navigation.PageHistory;
import org.silverpeas.mobile.client.common.network.NetworkHelper;
import org.silverpeas.mobile.client.common.network.rest.RestCallback;
import org.silverpeas.mobile.client.common.network.rest.RestMethod;
import org.silverpeas.mobile.client.common.network.rest.RestMethodCallbackOnlineOnly;
import org.silverpeas.mobile.client.common.resources.ResourcesManager;
import org.silverpeas.mobile.client.common.storage.LocalStorageHelper;
import org.silverpeas.mobile.shared.dto.DetailUserDTO;
import org.silverpeas.mobile.shared.dto.FullUserDTO;
import org.silverpeas.mobile.shared.dto.IFullUser;
import org.silverpeas.mobile.shared.dto.authentication.IUserProfile;
import org.silverpeas.mobile.shared.dto.authentication.UserProfileDTO;
import org.silverpeas.mobile.shared.exceptions.AuthenticationException;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: svu
 */
public class AuthentificationManager {

  private static AuthentificationManager instance = null;
  private static final String USER_CONNECTED_KEY = "userConnected";
  public static final String USER_PROFIL = "userProfil";
  private static final String DES_KEY = "LagTegshyeecnoc^";

  public static final String XSTKN = "X-STKN";
  public static final String XSilverpeasSession = "X-Silverpeas-Session";

  public static AuthentificationManager getInstance() {
    if (instance == null) {
      instance = new AuthentificationManager();
    }
    return instance;
  }

  public void addHeader(String key, String value) {
    LocalStorageHelper.getInstance().store(key, value);
  }

  public String getHeader(String key) {
    return LocalStorageHelper.getInstance().load(key);
  }

  public void storeUser(final DetailUserDTO user, final UserProfileDTO profil, String login,
      String password, String domainId) {
    String encryptedPassword = null;
    try {
      encryptedPassword = encryptPassword(password);
    } catch (InvalidCipherTextException e) {
      EventBus.getInstance().fireEvent(new ErrorEvent(e));
    }
    SpMobil.setUser(user, false);

    FullUserDTO u = new FullUserDTO(login, encryptedPassword, domainId, user);

    String maintainSession = ResourcesManager.getParam("maintain.session");
    if (maintainSession.equalsIgnoreCase("true")) {
      LocalStorageHelper.getInstance().store(USER_CONNECTED_KEY, u.getAutoBean());
      LocalStorageHelper.getInstance().store(USER_PROFIL, profil.getAutoBean());
    }
  }

  public void updateAvatarInCache(final String avatarData) {
    SpMobil.getUser().setAvatar(avatarData);
    SpMobil.getUserProfile().setAvatar(avatarData);
    FullUserDTO user = FullUserDTO.getBean(LocalStorageHelper.getInstance().load(USER_CONNECTED_KEY, IFullUser.class));
    user.setAvatar(avatarData);
    String maintainSession = ResourcesManager.getParam("maintain.session");
    if (maintainSession.equalsIgnoreCase("true")) {
      LocalStorageHelper.getInstance().store(USER_CONNECTED_KEY, user.getAutoBean());
      LocalStorageHelper.getInstance().store(USER_PROFIL, SpMobil.getUserProfile().getAutoBean());
    }
  }

  /**
   * Clean data in local storage.
   */
  public void clearLocalStorage() {
    LocalStorageHelper.getInstance().clear();
  }

  public FullUserDTO loadUser() {
    FullUserDTO user = FullUserDTO.getBean(LocalStorageHelper.getInstance().load(USER_CONNECTED_KEY, IFullUser.class));
    SpMobil.setUser(user, false);
    UserProfileDTO profil =
        UserProfileDTO.getBean(LocalStorageHelper.getInstance().load(USER_PROFIL, IUserProfile.class));
    SpMobil.setUserProfile(profil);
    return user;
  }

  /**
   * Decrypt password in local storage.
   *
   * @param passwordEncrysted
   * @return
   */
  public String decryptPassword(String passwordEncrysted) {
    TripleDesCipher cipher = new TripleDesCipher();
    cipher.setKey(DES_KEY.getBytes());
    String plainPassword = null;
    try {
      plainPassword = cipher.decrypt(passwordEncrysted);
    } catch (Exception e) {
      EventBus.getInstance().fireEvent(new ErrorEvent(e));
    }
    return plainPassword;
  }

  private String encryptPassword(String password) throws InvalidCipherTextException {
    TripleDesCipher cipher = new TripleDesCipher();
    cipher.setKey(DES_KEY.getBytes());
    String encryptedPassword = cipher.encrypt(password);
    return encryptedPassword;
  }

  public void authenticateOnSilverpeas(String login, String password, String domainId,
      Command attempt) {
    Notification.activityStart();
    if (NetworkHelper.isOnline()) {
      ServicesLocator.getRestServiceAuthentication(login, password, domainId).authentication(

          new RestCallback<UserProfileDTO>() {
            @Override
            public void onFailure(final RestMethod method, final Throwable throwable) {
              int statusError = method.getStatusCode();
              if (statusError == 403) {
                Notification.activityStop();
                SpMobil.displayMainPage();
              } else if (statusError == 401 || statusError == 500) {

                RestMethodCallbackOnlineOnly action = new RestMethodCallbackOnlineOnly<Boolean>() {
                  @Override
                  public void attempt() {
                    super.attempt();
                    ServicesLocator.getServiceConnection().userExist(login, domainId, this);
                  }

                  @Override
                  public void onFailure(final RestMethod method, final Throwable t) {
                    super.onFailure(method, t);
                    EventBus.getInstance().fireEvent(new AuthenticationErrorEvent(t));
                  }

                  @Override
                  public void onSuccess(final RestMethod method, final Boolean exist) {
                    super.onSuccess(method, exist);
                    if (exist) {
                      AuthenticationException ex = new AuthenticationException(
                          AuthenticationException.AuthenticationError.PwdNotAvailable);
                      ex.setLogin(login);
                      EventBus.getInstance().fireEvent(new AuthenticationErrorEvent(ex));
                    } else {
                      AuthenticationException ex = new AuthenticationException(
                          AuthenticationException.AuthenticationError.LoginNotAvailable);
                      ex.setLogin(login);
                      EventBus.getInstance().fireEvent(new AuthenticationErrorEvent(ex));
                    }
                  }
                };
                action.attempt();
              } else {
                EventBus.getInstance().fireEvent(new ErrorEvent(throwable));
              }
            }

            @Override
            public void onSuccess(final RestMethod method, final UserProfileDTO userProfile) {

              AuthentificationManager.getInstance().addHeader("X-STKN", method.getHeaders().get("X-STKN"));
              AuthentificationManager.getInstance().addHeader("X-Silverpeas-Session",
                  method.getHeaders().get("X-Silverpeas-Session"));

              SpMobil.setUserProfile(userProfile);


              RestMethodCallbackOnlineOnly action = new RestMethodCallbackOnlineOnly<DetailUserDTO>() {
                @Override
                public void attempt() {
                  super.attempt();
                  List<String> ids = new ArrayList<>();
                  ids.add(login);
                  ids.add(password);
                  ids.add(domainId);

                  ServicesLocator.getServiceConnection().login(ids, this);
                }

                @Override
                public void onSuccess(final RestMethod method, final DetailUserDTO user) {
                  super.onSuccess(method, user);
                  SpMobil.setUser(user, true);

                  ServicesLocator.getServiceTermsOfService().show(new RestMethodCallbackOnlineOnly<String>() {
                    @Override
                    public void onFailure(final RestMethod method, final Throwable throwable) {
                      if (throwable instanceof AuthenticationException) {
                        EventBus.getInstance().fireEvent(new AuthenticationErrorEvent(throwable));
                      } else {
                        EventBus.getInstance().fireEvent(new ErrorEvent(throwable));
                      }
                    }

                    @Override
                    public void onSuccess(final RestMethod method, final String showTermsOfServices) {
                      Notification.activityStop();
                      AuthentificationManager.getInstance().storeUser(user, userProfile, login,
                          password, domainId);
                      if (Boolean.parseBoolean(showTermsOfServices)) {
                        SpMobil.displayTermsOfServicePage();
                      } else {
                        if (attempt == null) {
                          SpMobil.displayMainPage();
                        } else {
                          attempt.execute();
                        }
                      }
                    }
                  });
                }

                @Override
                public void onFailure(final RestMethod method, final Throwable t) {
                  //super.onFailure(method, t);
                  GWT.log("Normaly never happen !!! " + t.getClass().getName() + " " + t.getMessage());
                  if (t instanceof AuthenticationException) {
                    EventBus.getInstance().fireEvent(new AuthenticationErrorEvent(t));
                  } else {
                    EventBus.getInstance().fireEvent(new ErrorEvent(t));
                  }
                }
              };
              action.attempt();
            }
          });
    } else {
      //TODO : terms of service in offline mode ?
      Notification.activityStop();
      loadUser();
      SpMobil.displayMainPage();
    }
  }

  public void injectAuthenticationHttpHeaders(RequestBuilder builder) {
    if (getHeader(AuthentificationManager.XSTKN) != null) {
      builder.setHeader(AuthentificationManager.XSTKN, getHeader(AuthentificationManager.XSTKN));
    }
    if (getHeader(AuthentificationManager.XSilverpeasSession) != null) {
      builder.setHeader(AuthentificationManager.XSilverpeasSession,
          getHeader(AuthentificationManager.XSilverpeasSession));
    }
  }

  public void logout() {
    // logout
    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, "/silverpeas/Logout");
    try {
      clearCache();
      builder.sendRequest("", new RequestCallback() {
        @Override
        public void onResponseReceived(final Request request, final Response response) {
          Notification.activityStop();
          AuthentificationManager.getInstance().clearLocalStorage();
          PageHistory.getInstance().clear();
          Notification.activityStop();
          SpMobil.getInstance().displayFirstPage();
          SpMobil.destroyMainPage();
        }
        @Override
        public void onError(final Request request, final Throwable throwable) {
          LocalStorageHelper.getInstance().storeBuildDate();
        }
      });
    } catch (RequestException e) {
      Window.alert(e.getMessage());
    }
  }

  public void clearCache() {
    // clear app cache
    RestMethodCallbackOnlineOnly action = new RestMethodCallbackOnlineOnly<Void>() {
      @Override
      public void attempt() {
        ServicesLocator.getServiceNavigation().clearAppCache(this);
      }

      @Override
      public void onFailure(final RestMethod method, final Throwable t) {

      }

      @Override
      public void onSuccess(final RestMethod method, final Void unused) {
      }
    };
    action.attempt();
  }
}
