// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/login/online_attempt.h"

#include <string>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/chromeos/login/auth_attempt_state.h"
#include "chrome/browser/chromeos/login/auth_attempt_state_resolver.h"
#include "chrome/browser/chromeos/login/user.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/gaia_constants.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_status.h"
#include "third_party/libjingle/source/talk/base/urlencode.h"

using content::BrowserThread;

namespace chromeos {

// static
const int OnlineAttempt::kClientLoginTimeoutMs = 10000;

OnlineAttempt::OnlineAttempt(AuthAttemptState* current_attempt,
                             AuthAttemptStateResolver* callback)
    : attempt_(current_attempt),
      resolver_(callback),
      weak_factory_(this),
      try_again_(true) {
  DCHECK(attempt_->user_type == User::USER_TYPE_REGULAR);
}

OnlineAttempt::~OnlineAttempt() {
  // Just to be sure.
  if (client_fetcher_.get())
    client_fetcher_->CancelRequest();
}

void OnlineAttempt::Initiate(Profile* auth_profile) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  client_fetcher_.reset(
      new GaiaAuthFetcher(this, GaiaConstants::kChromeOSSource,
                          auth_profile->GetRequestContext()));
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&OnlineAttempt::TryClientLogin, weak_factory_.GetWeakPtr()));
}

void OnlineAttempt::OnClientLoginSuccess(
    const GaiaAuthConsumer::ClientLoginResult& unused) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  VLOG(1) << "Online login successful!";

  weak_factory_.InvalidateWeakPtrs();

  if (attempt_->hosted_policy() == GaiaAuthFetcher::HostedAccountsAllowed &&
      attempt_->is_first_time_user()) {
    // First time user, and we don't know if the account is HOSTED or not.
    // Since we don't allow HOSTED accounts to log in, we need to try
    // again, without allowing HOSTED accounts.
    //
    // NOTE: we used to do this in the opposite order, so that we'd only
    // try the HOSTED pathway if GOOGLE-only failed.  This breaks CAPTCHA
    // handling, though.
    attempt_->DisableHosted();
    TryClientLogin();
    return;
  }
  TriggerResolve(LoginFailure::LoginFailureNone());
}

void OnlineAttempt::OnClientLoginFailure(
    const GoogleServiceAuthError& error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  weak_factory_.InvalidateWeakPtrs();

  if (error.state() == GoogleServiceAuthError::REQUEST_CANCELED) {
    if (try_again_) {
      try_again_ = false;
      // TODO(cmasone): add UMA tracking for this to see if we can remove it.
      LOG(ERROR) << "Login attempt canceled!?!?  Trying again.";
      TryClientLogin();
      return;
    }
    LOG(ERROR) << "Login attempt canceled again?  Already retried...";
  }

  if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS &&
      attempt_->is_first_time_user() &&
      attempt_->hosted_policy() != GaiaAuthFetcher::HostedAccountsAllowed) {
    // This was a first-time login, we already tried allowing HOSTED accounts
    // and succeeded.  That we've failed with INVALID_GAIA_CREDENTIALS now
    // indicates that the account is HOSTED.
    LOG(WARNING) << "Rejecting valid HOSTED account.";
    TriggerResolve(LoginFailure::FromNetworkAuthFailure(
                       GoogleServiceAuthError(
                           GoogleServiceAuthError::HOSTED_NOT_ALLOWED)));
    return;
  }

  if (error.state() == GoogleServiceAuthError::TWO_FACTOR) {
    LOG(WARNING) << "Two factor authenticated. Sync will not work.";
    TriggerResolve(LoginFailure::LoginFailureNone());

    return;
  }
  VLOG(2) << "ClientLogin attempt failed with " << error.state();
  TriggerResolve(LoginFailure::FromNetworkAuthFailure(error));
}

void OnlineAttempt::TryClientLogin() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  BrowserThread::PostDelayedTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&OnlineAttempt::CancelClientLogin, weak_factory_.GetWeakPtr()),
      base::TimeDelta::FromMilliseconds(kClientLoginTimeoutMs));

  client_fetcher_->StartClientLogin(
      attempt_->user_context.username,
      attempt_->user_context.password,
      GaiaConstants::kSyncService,
      attempt_->login_token,
      attempt_->login_captcha,
      attempt_->hosted_policy());
}

bool OnlineAttempt::HasPendingFetch() {
  return client_fetcher_->HasPendingFetch();
}

void OnlineAttempt::CancelRequest() {
  weak_factory_.InvalidateWeakPtrs();
}

void OnlineAttempt::CancelClientLogin() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (HasPendingFetch()) {
    LOG(WARNING) << "Canceling ClientLogin attempt.";
    CancelRequest();

    TriggerResolve(LoginFailure(LoginFailure::LOGIN_TIMED_OUT));
  }
}

void OnlineAttempt::TriggerResolve(
    const LoginFailure& outcome) {
  attempt_->RecordOnlineLoginStatus(outcome);
  client_fetcher_.reset(NULL);
  resolver_->Resolve();
}

}  // namespace chromeos