package com.stacksync.syncservice.storage;

import java.io.IOException;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import com.google.gson.Gson;
import com.stacksync.commons.models.User;
import com.stacksync.commons.models.Workspace;
import com.stacksync.syncservice.exceptions.storage.EndpointNotFoundException;
import com.stacksync.syncservice.exceptions.storage.ObjectNotFoundException;
import com.stacksync.syncservice.exceptions.storage.UnauthorizedException;
import com.stacksync.syncservice.exceptions.storage.UnexpectedStatusCodeException;
import com.stacksync.syncservice.storage.swift.LoginResponseObject;
import com.stacksync.syncservice.storage.swift.ServiceObject;
import com.stacksync.syncservice.util.Config;

public class SwiftManager extends StorageManager {

    private static StorageManager instance = null;
    private String authUrl;
    private String user;
    private String tenant;
    private String password;
    private String storageUrl;
    private String authToken;
    private DateTime expirationDate;

    private SwiftManager() {

        this.authUrl = Config.getSwiftAuthUrl();
        this.user = Config.getSwiftUser();
        this.tenant = Config.getSwiftTenant();
        this.password = Config.getSwiftPassword();
        this.expirationDate = DateTime.now();
    }

    public static synchronized StorageManager getInstance() {
        if (instance == null) {
            instance = new SwiftManager();
        }

        return instance;
    }

    @Override
    public void login() throws EndpointNotFoundException, UnauthorizedException, UnexpectedStatusCodeException,
            IOException {

        HttpClient httpClient = new DefaultHttpClient();

        try {
            HttpPost request = new HttpPost(authUrl);

            String body = String
                    .format("{\"auth\": {\"passwordCredentials\": {\"username\": \"%s\", \"password\": \"%s\"}, \"tenantName\":\"%s\"}}",
                    user, password, tenant);
            StringEntity entity = new StringEntity(body);
            entity.setContentType("application/json");
            request.setEntity(entity);
            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("404 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

            String responseBody = swiftResponse.getResponseBodyAsString();

            Gson gson = new Gson();
            LoginResponseObject loginResponse = gson.fromJson(responseBody, LoginResponseObject.class);

            this.authToken = loginResponse.getAccess().getToken().getId();

            Boolean endpointFound = false;

            for (ServiceObject service : loginResponse.getAccess().getServiceCatalog()) {

                if (service.getType().equals("object-store")) {
                    this.storageUrl = service.getEndpoints().get(0).getPublicURL();
                    endpointFound = true;
                    break;
                }
            }

            // get the token issue swift date
            DateTimeZone.setDefault(DateTimeZone.UTC);
            DateTimeFormatter dateStringFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
            DateTime issuedAt = dateStringFormat.parseDateTime(loginResponse.getAccess().getToken().getIssuedAt());

            // get the token expiration swift date
            dateStringFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ");
            DateTime expiresAt = dateStringFormat.parseDateTime(loginResponse.getAccess().getToken().getExpires());

            // calculate the period between these two dates and add it to our
            // current time because datetime can differ from Swift and this
            // device
            Period period = new Period(issuedAt, expiresAt);
            expirationDate = DateTime.now().plus(period);

            if (!endpointFound) {
                throw new EndpointNotFoundException();
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void createNewWorkspace(Workspace workspace) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        HttpClient httpClient = new DefaultHttpClient();

        String url = this.storageUrl + "/" + workspace.getSwiftContainer();

        try {

            HttpPut request = new HttpPut(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("401 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void removeUserToWorkspace(User owner, User user, Workspace workspace) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        String permissions = getWorkspacePermissions(owner, workspace);

        String tenantUser = Config.getSwiftTenant() + ":" + user.getSwiftUser();

        if (permissions.contains("," + tenantUser)) {
            permissions.replace("," + tenantUser, "");
        } else if (permissions.contains(tenantUser)) {
            permissions.replace(tenantUser, "");
        } else {
            return;
        }

        HttpClient httpClient = new DefaultHttpClient();
        String url = this.storageUrl + "/" + workspace.getSwiftContainer();

        try {

            HttpPut request = new HttpPut(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);
            request.setHeader(SwiftResponse.X_CONTAINER_READ, permissions);
            request.setHeader(SwiftResponse.X_CONTAINER_WRITE, permissions);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("404 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void grantUserToWorkspace(User owner, User user, Workspace workspace) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        String permissions = getWorkspacePermissions(owner, workspace);

        String tenantUser = Config.getSwiftTenant() + ":" + user.getSwiftUser();

        if (permissions.contains(tenantUser)) {
            return;
        }

        permissions += "," + tenantUser;

        HttpClient httpClient = new DefaultHttpClient();
        String url = this.storageUrl + "/" + workspace.getSwiftContainer();

        try {

            HttpPut request = new HttpPut(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);
            request.setHeader(SwiftResponse.X_CONTAINER_READ, permissions);
            request.setHeader(SwiftResponse.X_CONTAINER_WRITE, permissions);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("404 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void copyChunk(Workspace sourceWorkspace, Workspace destinationWorkspace, String chunkName) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        HttpClient httpClient = new DefaultHttpClient();

        String url = this.storageUrl + "/" + destinationWorkspace.getSwiftContainer() + "/"
                + chunkName;

        String copyFrom = "/" + sourceWorkspace.getSwiftContainer() + "/" + chunkName;

        try {

            HttpPut request = new HttpPut(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);
            request.setHeader(SwiftResponse.X_COPY_FROM, copyFrom);
            //request.setHeader("Content-Length", "0");

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("401 User unauthorized");
            }

            if (swiftResponse.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                throw new ObjectNotFoundException("404 Not Found");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void deleteChunk(Workspace workspace, String chunkName) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        HttpClient httpClient = new DefaultHttpClient();

        String url = this.storageUrl + "/" + workspace.getSwiftContainer() + "/" + chunkName;

        try {

            HttpDelete request = new HttpDelete(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("401 User unauthorized");
            }

            if (swiftResponse.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                throw new ObjectNotFoundException("404 Not Found");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public void deleteWorkspace(Workspace workspace) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        HttpClient httpClient = new DefaultHttpClient();

        String url = this.storageUrl + "/" + workspace.getSwiftContainer();

        try {

            HttpDelete request = new HttpDelete(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("401 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    private String getWorkspacePermissions(User user, Workspace workspace) throws Exception {

        if (!isTokenActive()) {
            login();
        }

        HttpClient httpClient = new DefaultHttpClient();

        String url = this.storageUrl + "/" + workspace.getSwiftContainer();

        try {

            HttpHead request = new HttpHead(url);
            request.setHeader(SwiftResponse.X_AUTH_TOKEN, authToken);

            HttpResponse response = httpClient.execute(request);

            SwiftResponse swiftResponse = new SwiftResponse(response);

            if (swiftResponse.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new UnauthorizedException("404 User unauthorized");
            }

            if (swiftResponse.getStatusCode() < 200 || swiftResponse.getStatusCode() >= 300) {
                throw new UnexpectedStatusCodeException("Unexpected status code: " + swiftResponse.getStatusCode());
            }

            // We suppose there are the same permissions for read and write
            Header containerWriteHeader = swiftResponse.getResponseHeader(SwiftResponse.X_CONTAINER_WRITE);

            if (containerWriteHeader == null) {
                return "";
            }

            return containerWriteHeader.getValue();

        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    private boolean isTokenActive() {
        return DateTime.now().isBefore(expirationDate);
    }
}