package cz.hsrs.servlet.provider;

import java.io.BufferedReader;
import java.io.IOException;
import java.sql.SQLException;

import javax.naming.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;

import cz.hsrs.core.WebServiceException;
import net.sf.json.JSON;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import net.sf.json.JsonConfig;
import net.sf.json.util.CycleDetectionStrategy;

import cz.hsrs.db.model.NoItemFoundException;
import cz.hsrs.db.model.insert.UnitInsert;
import cz.hsrs.db.util.UtilFactory;
import cz.hsrs.servlet.feeder.ServiceParameters;
import cz.hsrs.servlet.security.LoginUser;


/**
 * @author mkepka
 *
 */
public class ManagementService extends DBServlet {

    private static final long serialVersionUID = 1L;
    private static final String JSON_CONTENT = "application/json";
    private static final String TEXT_CONTENT = "plain/text";

    // POST
    private static final String INSERT_UNIT = "InsertUnit";
    private static final String INSERT_SENSOR = "InsertSensor";

    // PUT
    private static final String UPDATE_UNIT   = "UpdateUnit";
    private static final String UPDATE_SENSOR = "UpdateSensor";

    // DELETE
    private static final String DELETE_UNIT   = "DeleteUnit";
    private static final String DELETE_SENSOR = "DeleteSensor";


    private static final JsonConfig DEFAULT_JSON_CONFIG;

    static {
        DEFAULT_JSON_CONFIG = new JsonConfig();
        DEFAULT_JSON_CONFIG.setIgnoreTransientFields(true);
        DEFAULT_JSON_CONFIG.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
        DEFAULT_JSON_CONFIG.setJsonPropertyFilter((source, name, value) -> (value == null));
    }

    private UtilFactory db;
    
    @Override
    public void init() throws ServletException {
        super.init();
        
        try {
            db = new UtilFactory();
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    private static String requestBodyAsString(HttpServletRequest request) {
        if (request.getContentLength() <= 0) {
            return "";
        }
        try {
            String line;
            StringBuilder jb = new StringBuilder();
            BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                jb.append(line);
            }
            return jb.toString();
        } catch (IOException e) {
            return "";
        }
    }

    private static JSONObject stringToJson(String jsonString) {
        try {
            return JSONObject.fromObject(jsonString);
        } catch (JSONException e){
            throw new JSONException(e.getMessage());
        }
    }
/* -- Prihlasovani -- */

    private boolean isNotAuthorized(String user) {
        if(user == null || user.isEmpty()) {
            return true;
        }
        try {
            String testLang = db.userUtil.getUserLanguage(user);
            if (testLang.isEmpty()) {
                return true;
            }
        } catch (SQLException | NoItemFoundException e) {
            return true;
        }
        return false;
    }

    private LoginUser getUserBySession(HttpServletRequest request) throws ServletException {
        try {
            return getAuthenticatedLoginUser(request);
        } catch (AuthenticationException e) {
            throw new ServletException("Authentication failure for request "+ request.getQueryString());
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.addHeader(HttpHeaders.CONTENT_TYPE, JSON_CONTENT);
        response.addHeader("Access-Control-Allow-Origin", "*");
        
        LoginUser loggedUser = getUserBySession(request);

        String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
        if (contentType != null && !contentType.equalsIgnoreCase(JSON_CONTENT)) {
            throw new ServletException("Received request does not contain JSON data. " +
                    "Only request with 'application/json' header could be accepter. " + request.getQueryString()
            );
        }

        String body = requestBodyAsString(request);
        if (body.isEmpty()) {
            throw new ServletException("Request does not contain any content.");
        }

        JSON bodyResponse;
        try {
            JSONObject bodyJson = stringToJson(body);
            String operationMode = request.getParameter(ServiceParameters.OPERATION);
            switch (operationMode) {
                case INSERT_UNIT:   bodyResponse = insertUnit(bodyJson, loggedUser.getUserName());   break;
                case INSERT_SENSOR: bodyResponse = insertSensor(bodyJson, loggedUser.getUserName()); break;
                default: throw new ServletException(
                        String.format("No operation specified! Allowed: [%s, %s].", INSERT_UNIT, INSERT_SENSOR)
                );
            }
        } catch (Exception e) {
            JSONObject msg = new JSONObject();
            msg.put("message", e.getMessage());
            bodyResponse = msg;
            response.setStatus(400);
        }

        bodyResponse.write(response.getWriter());
    }

    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.addHeader(HttpHeaders.CONTENT_TYPE, JSON_CONTENT);
        response.addHeader("Access-Control-Allow-Origin", "*");

        LoginUser loggedUser = getUserBySession(request);

        String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
        if (contentType != null && !contentType.equalsIgnoreCase(JSON_CONTENT)) {
            throw new ServletException("Received request does not contain JSON data. " +
                    "Only request with 'application/json' header could be accepter. " + request.getQueryString()
            );
        }

        String body = requestBodyAsString(request);
        if (body.isEmpty()) {
            throw new ServletException("Request does not contain any content.");
        }

        JSON bodyResponse;
        try {
            JSONObject bodyJson = stringToJson(body);
            String operationMode = request.getParameter(ServiceParameters.OPERATION);
            switch (operationMode) {
                case UPDATE_UNIT:   bodyResponse = updateUnit(bodyJson);   break;
                case UPDATE_SENSOR: bodyResponse = updateSensor(bodyJson); break;
                default: throw new ServletException(
                        String.format("No operation specified! Allowed: [%s, %s].", UPDATE_UNIT, UPDATE_SENSOR)
                );
            }
        } catch (Exception e) {
            JSONObject msg = new JSONObject();
            msg.put("message", e.getMessage());
            bodyResponse = msg;
            response.setStatus(400);
        }

        bodyResponse.write(response.getWriter());
    }

    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.addHeader(HttpHeaders.CONTENT_TYPE, JSON_CONTENT);
        response.addHeader("Access-Control-Allow-Origin", "*");

        LoginUser loggedUser = getUserBySession(request);

        String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
        if (contentType != null && !contentType.equalsIgnoreCase(JSON_CONTENT)) {
            throw new ServletException("Received request does not contain JSON data. " +
                    "Only request with 'application/json' header could be accepter. " + request.getQueryString()
            );
        }

        String body = requestBodyAsString(request);
        if (body.isEmpty()) {
            throw new ServletException("Request does not contain any content.");
        }

        JSON bodyResponse;
        try {
            JSONObject bodyJson = stringToJson(body);
            String operationMode = request.getParameter(ServiceParameters.OPERATION);
            switch (operationMode) {
                case DELETE_UNIT:   bodyResponse = deleteUnit(bodyJson);   break;
                case DELETE_SENSOR: bodyResponse = deleteSensor(bodyJson); break;
                default:  {
                    String msg = String.format("No operation specified! Allowed: [%s, %s].", DELETE_UNIT, DELETE_SENSOR);
                    throw new ServletException(msg);
                }
            }
        } catch (WebServiceException e) {
            JSONObject msg = new JSONObject();
            msg.put("message", e.getMessage());
            bodyResponse = msg;
            response.setStatus(e.getCode());
        } catch (Exception e) {
            JSONObject msg = new JSONObject();
            msg.put("message", e.getMessage());
            bodyResponse = msg;
            response.setStatus(400);
        }

        bodyResponse.write(response.getWriter());
    }

    private JSON insertUnit(JSONObject unitJson, String user) throws SQLException, NoItemFoundException {
        UnitInsert newUnit = db.manUtil.insertUnit(unitJson, user);
        return JSONSerializer.toJSON(newUnit, DEFAULT_JSON_CONFIG);
    }

    private JSON insertSensor(JSONObject sensorJson, String user) throws SQLException, NoItemFoundException {
        UnitInsert sensorsToUnit = db.manUtil.insertSensor(sensorJson, user);
        return JSONSerializer.toJSON(sensorsToUnit, DEFAULT_JSON_CONFIG);
    }

    private JSON updateUnit(JSONObject unitJson) throws SQLException, NoItemFoundException {
        UnitInsert updatedUnit = db.manUtil.updateUnit(unitJson);
        return JSONSerializer.toJSON(updatedUnit, DEFAULT_JSON_CONFIG);
    }

    private JSON updateSensor(JSONObject sensorJson) throws SQLException, NoItemFoundException {
        UnitInsert updatedSensors = db.manUtil.updateSensor(sensorJson);
        return JSONSerializer.toJSON(updatedSensors, DEFAULT_JSON_CONFIG);
    }

    private JSON deleteUnit(JSONObject unitJson) throws SQLException, NoItemFoundException {
        Long deletedUnit = db.manUtil.deleteUnit(unitJson);
        if (deletedUnit != null) {
            JSONObject resultJson = new JSONObject();
            resultJson.put("message", String.format("Unit '%s' was successfully deleted.", deletedUnit));
            return resultJson;
        } else {
            throw new SQLException("Unit can not be deleted.");
        }
    }

    private JSON deleteSensor(JSONObject unitJson) throws SQLException, NoItemFoundException {
        UnitInsert deletedSensors = db.manUtil.deleteSensor(unitJson);
        return JSONSerializer.toJSON(deletedSensors, DEFAULT_JSON_CONFIG);
    }
}