package com.gebweb.items.server;

import java.util.Date;
import java.util.logging.Logger;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;

import com.gebweb.items.server.util.DatastoreUtils;
import com.gebweb.items.server.util.MailUtils;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;

public class DatastoreRealm extends AuthorizingRealm {
  private static final Logger log = Logger.getLogger(DatastoreRealm.class.getName());  

  @Override
  public boolean supports(AuthenticationToken authToken) {
    return (authToken instanceof UsernamePasswordToken);
  }
  
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
    return null;
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken)
      throws AuthenticationException {
    if (authToken == null) {
      log.warning("Auth token is null");
      return null;
    }
    if (authToken instanceof UsernamePasswordToken) {
      UsernamePasswordToken upToken = (UsernamePasswordToken)authToken;
      String userName = upToken.getUsername();
      PasswordSalt ps = getPasswordForUser(userName);
      if (ps == null) {
        log.warning("No account found for username: " + userName);
        return null;
      }
      SimpleAuthenticationInfo authInfo = new SimpleAuthenticationInfo(userName, ps.getPassword(), userName);
      authInfo.setCredentialsSalt(new SimpleByteSource(ps.getSalt()));
      return authInfo;
    }
    throw new UnsupportedTokenException();
  }
  
  public static boolean insertUsernamePassword(String username, String password) {
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Query q = new Query("Login");
    q.setFilter(CompositeFilterOperator.and(
    		Query.FilterOperator.EQUAL.of("username", username),
    		Query.FilterOperator.EQUAL.of("active", true)));
    
    int resCount = DatastoreUtils.getQueryResultSize(q);
    if (resCount > 0) {
      log.warning("Username " + username + " already exists!");
      return false;
    }
    
    // Make a hash:
    String salt = "" + Math.random() + (new Date()).toString() + username;
    String hash = simpleSaltedHash(username, salt, password);
    
    // Make an activation code:
    String salt2 = "" + (new Date()).toString() + Math.random();
    Sha256Hash sha256Hash = new Sha256Hash(username, new SimpleByteSource(salt2).getBytes());
    String activationCode = sha256Hash.toHex();
    
    Entity ent = new Entity("Login");
    ent.setProperty("username", username);
    ent.setProperty("salt", salt);
    ent.setProperty("password", hash);
    ent.setProperty("active", false);
    ent.setProperty("activation_code", activationCode);
    datastore.put(ent);
    
    // Send email to specified account with activation code:
    MailUtils.sendMail(username, null, "SkyLib account activation",
                       "Open http://www.skylib.com/activate/" + activationCode +
                       " to finish the account activation process\n\n" +
                       "-The SkyLib Team-");
    
    return true;
  }
  
  public static boolean activate(String activationCode) {
    if (activationCode == null) {
      log.warning("Activation code is null");
      return false;
    }
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Query q = new Query("Login");
    q.setFilter(Query.FilterOperator.EQUAL.of("activation_code", activationCode));
    PreparedQuery pq = datastore.prepare(q);
    Entity ent = pq.asSingleEntity();
    if (ent != null) {
      ent.setProperty("active", true);
      datastore.put(ent);
      return true;
    }
    log.warning("Activation code not found");
    return false;
  }

  private static String simpleSaltedHash(String username, String salt, String password) {
    Sha256Hash sha256Hash = new Sha256Hash(password, (new SimpleByteSource(salt)).getBytes());
    String result = sha256Hash.toHex();
  
    log.fine(username + " simple salted hash: " + result);
    return result;
  }
  
  private static PasswordSalt getPasswordForUser(String username) {
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Query q = new Query("Login");
    q.setFilter(CompositeFilterOperator.and(
    		Query.FilterOperator.EQUAL.of("username", username),
    		Query.FilterOperator.EQUAL.of("active", true)));
    PreparedQuery pq = datastore.prepare(q);
    Entity ent = pq.asSingleEntity();
    if (ent != null) {
      PasswordSalt ret = new PasswordSalt((String)ent.getProperty("password"),
                                          (String)ent.getProperty("salt"));
      return ret;
    }
    return null;
  }
}
