package com.github.dadiyang.httpinvoker.interfaces;

import com.alibaba.fastjson.JSON;
import com.github.dadiyang.httpinvoker.HttpApiProxyFactory;
import com.github.dadiyang.httpinvoker.entity.City;
import com.github.dadiyang.httpinvoker.entity.ResultBean;
import com.github.dadiyang.httpinvoker.entity.ResultBeanWithStatusAsCode;
import com.github.dadiyang.httpinvoker.requestor.ResultBeanResponseProcessor;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

import static com.github.dadiyang.httpinvoker.util.CityUtil.createCities;
import static com.github.dadiyang.httpinvoker.util.CityUtil.createCity;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.*;

/**
 * 对一些错误值进行测试
 *
 * @author dadiyang
 * date 2019/1/10
 */
public class CityServiceErrorTest {
    private static final int PORT = 18888;
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));
    private CityService cityService;

    @Before
    public void setUp() throws Exception {
        System.setProperty("api.url.city.host", "http://localhost:" + PORT);
        System.setProperty("api.url.city.host2", "http://localhost:" + PORT);
        HttpApiProxyFactory httpApiProxyFactory = new HttpApiProxyFactory();
        cityService = httpApiProxyFactory.getProxy(CityService.class);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIllegalPathVariable() {
        cityService.getCityRest(null);
    }

    /**
     * 测试类上面加的重试注解
     */
    @Test
    public void testClassAnnotatedRetry() {
        wireMockRule.stubFor(get(urlPathEqualTo("/city/getById")).willReturn(notFound()));
        try {
            cityService.getCity(1);
            fail("前面应该报异常");
        } catch (Exception e) {
            e.printStackTrace();
            assertEquals(e.getCause().getClass(), IOException.class);
        }
        // 前面报错后重试 3 次
        wireMockRule.verify(3, RequestPatternBuilder.allRequests().withUrl("/city/getById?id=1"));
    }

    /**
     * 测试方法上加的重试注解
     */
    @Test
    public void testMethodAnnotatedRetry() {
        long start = System.currentTimeMillis();
        wireMockRule.stubFor(get(urlPathEqualTo("/city/allCities")).willReturn(serverError()));
        try {
            cityService.getAllCities();
            fail("前面应该报异常");
        } catch (Exception e) {
            e.printStackTrace();
            assertEquals(e.getCause().getClass(), IOException.class);
        }
        long timeConsume = System.currentTimeMillis() - start;
        assertTrue("重试之前会休眠3000秒,因此2次请求,需要重试1次,即需要在3-6秒完成测试",
                timeConsume > 3000 && timeConsume < 6000);
        // 前面报错后重试 2 次
        wireMockRule.verify(2, RequestPatternBuilder.allRequests().withUrl("/city/allCities"));
    }

    /**
     * 只在50x的时候重试
     */
    @Test
    public void testOnlyRetry50x() {
        List<City> mockCities = createCities();
        String uri = "/city/save";
        wireMockRule.stubFor(post(urlPathEqualTo(uri)).willReturn(serverError()));
        try {
            cityService.saveCities(mockCities);
            fail("前面应该报异常");
        } catch (Exception e) {
            e.printStackTrace();
            assertEquals(e.getCause().getClass(), IOException.class);
        }
        // 前面报只对 50x 的错误尝试 2 次
        wireMockRule.verify(2, RequestPatternBuilder.allRequests().withUrl(uri));

        wireMockRule.resetAll();
        wireMockRule.stubFor(post(urlPathEqualTo(uri)).willReturn(notFound()));
        try {
            cityService.saveCities(mockCities);
            fail("前面应该报异常");
        } catch (Exception e) {
            assertEquals(e.getCause().getClass(), IOException.class);
        }
        // 前面报错后应该不重试
        wireMockRule.verify(1, RequestPatternBuilder.allRequests().withUrl(uri));
    }

    @Test(expected = IllegalStateException.class)
    public void getAllCitiesWithResultBeanResponseProcessor() {
        List<City> mockCities = createCities();
        String uri = "/city/allCities";
        wireMockRule.stubFor(get(urlEqualTo(uri)).willReturn(aResponse().withBody(JSON.toJSONString(new ResultBean<List<City>>(1, mockCities)))));
        CityService cityServiceWithResultBeanResponseProcessor = HttpApiProxyFactory.newProxy(CityService.class, new ResultBeanResponseProcessor());
        cityServiceWithResultBeanResponseProcessor.getAllCities();
    }

    @Test(expected = IllegalStateException.class)
    public void getCityWithResultBean() throws UnsupportedEncodingException {
        String cityName = "北京";
        String uri = "/city/getCityByName?name=" + URLEncoder.encode(cityName, "UTF-8");
        City city = createCity(cityName);
        ResultBean<City> mockCityResult = new ResultBean<City>(0, city);
        wireMockRule.stubFor(get(urlEqualTo(uri))
                .willReturn(aResponse().withBody(JSON.toJSONString(mockCityResult))));
        CityService cityServiceWithResultBeanResponseProcessor = HttpApiProxyFactory.newProxy(CityService.class, new ResultBeanResponseProcessor());
        cityServiceWithResultBeanResponseProcessor.getCityWithResultBean(cityName);
    }

    @Test(expected = IllegalStateException.class)
    public void getCityWithStatusCode() throws UnsupportedEncodingException {
        String cityName = "北京";
        String uri = "/city/getCityByName?name=" + URLEncoder.encode(cityName, "UTF-8");
        ResultBeanWithStatusAsCode<City> mockCityResult = new ResultBeanWithStatusAsCode<City>("出错啦~", 0);
        wireMockRule.stubFor(get(urlEqualTo(uri))
                .willReturn(aResponse().withBody(JSON.toJSONString(mockCityResult))));
        CityService cityServiceWithResultBeanResponseProcessor = HttpApiProxyFactory.newProxy(CityService.class, new ResultBeanResponseProcessor());
        cityServiceWithResultBeanResponseProcessor.getCityWithStatusCode(cityName);
    }

    @Test(expected = IllegalArgumentException.class)
    public void invalidMethod() {
        cityService.invalidMethod();
    }

    @Test
    public void invalidMethodWithoutHttpReq() {
        try {
            cityService.invalidMethodWithoutHttpReq();
            fail("should throws an exception when invoke the method without annotated with @HttpReq");
        } catch (IllegalStateException e) {
            assertEquals(e.getMessage(), "this proxy only implement those HttpReq-annotated method, please add a @HttpReq on it.");
        }
    }
}