package me.chyxion.tigon.webmvc.test;

import java.util.Map;
import java.util.Map.Entry;
import com.alibaba.fastjson.JSON;
import javax.annotation.PostConstruct;
import org.springframework.util.Assert;
import com.alibaba.fastjson.JSONObject;
import java.nio.charset.StandardCharsets;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Controler Test Tool
 * @version 0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Jun 12, 2014 8:13:52 PM
 */
public final class ControllerTestTool {

    @Autowired
    public WebApplicationContext applicationContext;
    public MockMvc mvc;

    @PostConstruct
    public void init() {
        mvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
    }

    public ResultActions post(String url, Map<String, ?> params) {
        return request(MockMvcRequestBuilders.post(url), params);
    }

    public ResultActions post(String url) {
        return request(MockMvcRequestBuilders.post(url), (Map<String, ?>) null);
    }

    public ResultActions post(String url, JSON body) {
        return request(MockMvcRequestBuilders.post(url), body);
    }

    public ResultActions get(String url, Map<String, ?> params) {
        return request(MockMvcRequestBuilders.get(url), params);
    }

    public ResultActions get(String url) {
        return request(MockMvcRequestBuilders.get(url), (Map<String, ?>) null);
    }

    public ResultActions put(String url, Map<String, ?> params) {
        return request(MockMvcRequestBuilders.put(url), params);
    }

    public ResultActions put(String url) {
        return request(MockMvcRequestBuilders.put(url), (Map<String, ?>) null);
    }

    public ResultActions put(String url, JSON body) {
        return request(MockMvcRequestBuilders.put(url), body);
    }

    public ResultActions delete(String url, Map<String, ?> params) {
        return request(MockMvcRequestBuilders.delete(url), params);
    }

    public ResultActions delete(String url) {
        return request(MockMvcRequestBuilders.delete(url), (Map<String, ?>) null);
    }

    public ResultActions delete(String url, JSON body) {
        return request(MockMvcRequestBuilders.delete(url), body);
    }

    public ResultActions request(
            MockHttpServletRequestBuilder requestBuilder,
            Map<String, ?> params) {
        if (params != null && params.size() > 0) {
            for (Entry<String, ?> param : params.entrySet()) {
                Object v = param.getValue();
                if (v != null) {
                    String sv = null;
                    if (v instanceof String) {
                        sv = (String) v;
                    }
                    else {
                        sv = JSON.toJSONString(v);
                    }
                    requestBuilder.param(param.getKey(), sv);
                }
            }
        }
        return request(requestBuilder);
    }

    /**
     * @param requestBuilder
     * @param body
     * @return
     */
    public ResultActions request(
            MockHttpServletRequestBuilder requestBuilder,
            JSON body) {
        Assert.state(body != null, "Requst Body Could Not Be Null");
        requestBuilder.contentType(MediaType.APPLICATION_JSON);
        requestBuilder.content(body.toString());
        return request(requestBuilder);
    }

    /**
     * assert json value equals
     * @param result
     * @param path
     * @param value
     * @return
     */
    public ResultActions assertJsonValue(ResultActions result, final String path, final Object value) {
        try {
            return result.andExpect(status().isOk())
                    .andExpect(new ResultMatcher() {
                        @Override
                        public void match(MvcResult result) throws Exception {
                            JSONObject joResp = JSON.parseObject(result.getResponse().getContentAsString());
                            Object v = null;
                            for (String p : path.split("\\.")) {
                                if (v == null) {
                                    v = joResp.get(p);
                                }
                                else {
                                    v = ((JSONObject) v).get(p);
                                }
                            }
                            Assert.isTrue(value.equals(v));
                        }
                    });
        }
        catch (Exception e) {
            throw new IllegalStateException(
                    "Assert Mock HTTP Request JSON Result Value Error Caused", e);
        }
    }

    public ResultActions assertForward(ResultActions result, String view) {
        try {
            return result.andExpect(status().isOk())
                    .andExpect(forwardedUrl(view));
        }
        catch (Exception e) {
            throw new IllegalStateException(
                    "Assert Mock HTTP Request Forward Error Caused", e);
        }
    }

    public ResultActions print(ResultActions result) {
        try {
            return result.andDo(MockMvcResultHandlers.print());
        }
        catch (Exception e) {
            throw new IllegalStateException(
                    "Print Mock HTTP Request Result Error Caused", e);
        }
    }

    ResultActions request(
            MockHttpServletRequestBuilder requestBuilder) {
        requestBuilder.characterEncoding(StandardCharsets.UTF_8.name());
        try {
            return mvc.perform(requestBuilder);
        }
        catch (Exception e) {
            throw new IllegalStateException(
                    "Mock HTTP Request Error Caused", e);
        }
    }
}
