package com.gebweb.items.server.facebook;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
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.pam.UnsupportedTokenException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.gebweb.items.server.util.DatastoreUtils;
import com.gebweb.skylib.common.data.UserProfile;
import com.gebweb.skylib.common.util.StreamUtil;
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.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.gson.Gson;

public class FBRealm extends AuthorizingRealm {
  private static final Logger log = Logger.getLogger(FBRealm.class.getName());  
  private static final String APP_SECRET = "6e7c1f39453e3033b6a912d7650bca7c";
  private static final String APP_ID = "464853176867629";

  @Override
  public boolean supports(AuthenticationToken authToken) {
    return (authToken instanceof FacebookToken);
  }

  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
    return new FacebookAuthorizationInfo();
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken)
    throws AuthenticationException {
    if (authToken == null) {
      log.warning("Auth token is null");
      return null;
    }
    if (authToken instanceof FacebookToken) {
      FacebookToken fbToken = (FacebookToken)authToken;
      
      // See if we already have the access_token
      String token = fbToken.getAccessToken(); 
      if (token != null && token.trim().length() > 0) {
        // Lookup Facebook user data
        UserProfile profile;
        try {
          profile = getUserProfile(token);
          return new FacebookAuthenticationInfo(profile, this.getName());
        } catch (IOException e) {
          log.severe("Error during Facebook profile retrieval: " + e.getMessage());
          e.printStackTrace();
        }
        return null;
      }
      
      // Flow when access_token is not available and must be retrieved with code
      String code = fbToken.getCode();
      if (code != null && code.trim().length() > 0) {
        URL authUrl;
        try {
          authUrl = new URL("https://graph.facebook.com/oauth/access_token?client_id=" +
            APP_ID + "&redirect_uri=http://www.skylib.com/fbLogin" +
            "&client_secret=" + APP_SECRET +
            "&code=" + code);
          HttpURLConnection connection = (HttpURLConnection) authUrl.openConnection();
          connection.setRequestMethod("GET");
          connection.connect();
          if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
            log.fine("OK connection code");
            String replyBody = StreamUtil.readAll(connection.getInputStream());
            log.info("Got Facebook reply: " + replyBody);

            // Parse returned value
            String accessToken = getAccessToken(replyBody);

            // Lookup Facebook user data
            UserProfile profile = getUserProfile(accessToken);
            return new FacebookAuthenticationInfo(profile, this.getName());
          } 
          log.severe("Error connection code: " + connection.getResponseCode());
        } catch (MalformedURLException e1) {
          log.severe("Malformed facebook auth url: " + e1.getMessage());
          e1.printStackTrace();
          throw new AuthenticationException(e1);
        } catch (IOException ioe) {
          log.severe("IOException during FB login: " + ioe.getMessage());
          ioe.printStackTrace();
          throw new AuthenticationException(ioe);
        } catch (Throwable e) {
          log.severe("Exception during FB login: " + e.getMessage());
          e.printStackTrace();
        }
      }
      return null;
    }
    throw new UnsupportedTokenException();
  }

  public static FacebookProfile getFacebookProfile(String replyBody) {
    Gson gson = new Gson();
    return gson.fromJson(replyBody, FacebookProfile.class);
  }

  public static String getAccessToken(String replyBody) {
    String[] tokens = replyBody.split("&");
    for (String token : tokens) {
      String[] kv = token.split("=");
      if (kv.length == 2) {
        if ("access_token".equals(kv[0])) {
          return kv[1];
        }
      }
    }
    return null;
  }

  public static UserProfile getUserProfile(String accessToken) throws IOException {
    URL url = new URL("https://graph.facebook.com/me?access_token=" + accessToken);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    connection.connect();
    if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
      log.fine("OK connection code");
      String replyBody = StreamUtil.readAll(connection.getInputStream());
      log.info("Got Facebook me reply: " + replyBody);      
      FacebookProfile fbProfile = getFacebookProfile(replyBody);
      storeFbProfile(accessToken, fbProfile);
      UserProfile profile = new UserProfile(-1);
      profile.setName(fbProfile.getName());
      profile.setFacebookEmail(fbProfile.getEmail());
      profile.setFacebookUserId(fbProfile.getId());
      return profile;
    } 
    log.severe("Error connection code: " + connection.getResponseCode());
    return null;
  }  
  
  private static void storeFbProfile(String accessToken, FacebookProfile profile) {
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    
    // First, remove existing data
    Query q = new Query("FBLogin");
    q.setFilter(Query.FilterOperator.EQUAL.of("id", profile.getId()));
    int resCount = DatastoreUtils.getQueryResultSize(q);    
    if (resCount > 0) {
      PreparedQuery pq = datastore.prepare(q);
      List<Entity> ents = pq.asList(FetchOptions.Builder.withDefaults());
      List<Key> keys = new ArrayList<Key>();
      for (Entity ent : ents) {
        keys.add(ent.getKey());
      }
      datastore.delete(keys);
    }
    
    // Store new data
    Entity ent = new Entity("FBLogin");
    ent.setProperty("id", profile.getId());
    ent.setProperty("email", profile.getEmail());
    ent.setProperty("name", profile.getName());
    ent.setProperty("locale", profile.getLocale());
    ent.setProperty("access_token", accessToken);
    datastore.put(ent);
  }
}
