/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.struts2.rest;

import org.apache.struts2.ActionContext;
import org.apache.struts2.DefaultUnknownHandlerManager;
import org.apache.struts2.ModelDriven;
import org.apache.struts2.ObjectFactory;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.entities.ActionConfig;
import org.apache.struts2.config.entities.InterceptorMapping;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.mock.MockActionProxy;
import org.apache.struts2.mock.MockInterceptor;
import org.apache.struts2.ognl.DefaultOgnlBeanInfoCacheFactory;
import org.apache.struts2.ognl.DefaultOgnlExpressionCacheFactory;
import org.apache.struts2.ognl.OgnlUtil;
import org.apache.struts2.util.XWorkTestCaseHelper;
import jakarta.servlet.http.HttpServletResponse;
import junit.framework.TestCase;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.ognl.StrutsOgnlGuard;
import org.apache.struts2.result.HttpHeaderResult;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.struts2.ognl.OgnlCacheFactory.CacheType.BASIC;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;

public class RestActionInvocationTest extends TestCase {

    RestActionInvocation restActionInvocation;
    MockHttpServletRequest request;
    MockHttpServletResponse response;

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        restActionInvocation = new RestActionInvocationTester();
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
        ServletActionContext.setRequest(request);
        ServletActionContext.setResponse(response);

    }

    /**
     * Test the correct action results: null, String, HttpHeaders, Result
     * @throws Exception
     */
    public void testSaveResult() throws Exception {

        Object methodResult = "index";
        ActionConfig actionConfig = restActionInvocation.getProxy().getConfig();
        assertEquals("index", restActionInvocation.saveResult(actionConfig, methodResult));

        setUp();
        methodResult = new DefaultHttpHeaders("show");
        assertEquals("show", restActionInvocation.saveResult(actionConfig, methodResult));
        assertEquals(methodResult, restActionInvocation.httpHeaders);

        setUp();
        methodResult = new HttpHeaderResult(HttpServletResponse.SC_ACCEPTED);
        assertEquals(null, restActionInvocation.saveResult(actionConfig, methodResult));
        assertEquals(methodResult, restActionInvocation.createResult());

        setUp();
        try {
            methodResult = new Object();
            restActionInvocation.saveResult(actionConfig, methodResult);

            // ko
            assertFalse(true);

        } catch (ConfigurationException c) {
            // ok, object not allowed
        }
    }

    /**
     * Test the target selection: exception, error messages, model and null
     * @throws Exception
     */
    public void testSelectTarget() throws Exception {

        // Exception
        Exception e = new Exception();
        restActionInvocation.getStack().set("exception", e);
        restActionInvocation.selectTarget();
        assertEquals(e, restActionInvocation.target);

        // Error messages
        setUp();
        String actionMessage = "Error!";
        RestActionSupport action = (RestActionSupport)restActionInvocation.getAction();
        action.addActionError(actionMessage);
        Map<String, Object> errors = new HashMap<String, Object>();
        List<String> list = new ArrayList<String>();
        list.add(actionMessage);
        errors.put("actionErrors", list);
        restActionInvocation.selectTarget();
        assertEquals(errors, restActionInvocation.target);

        // Model with get and no content in post, put, delete
        setUp();
        RestAction restAction = (RestAction)restActionInvocation.getAction();
        List<String> model = new ArrayList<String>();
        model.add("Item");
        restAction.model = model;
        request.setMethod("GET");
        restActionInvocation.selectTarget();
        assertEquals(model, restActionInvocation.target);
        request.setMethod("POST");
        restActionInvocation.selectTarget();
        assertEquals(null, restActionInvocation.target);
        request.setMethod("PUT");
        restActionInvocation.selectTarget();
        assertEquals(null, restActionInvocation.target);
        request.setMethod("DELETE");
        restActionInvocation.selectTarget();
        assertEquals(null, restActionInvocation.target);

        // disable content restriction to GET only
        model = new ArrayList<String>();
        model.add("Item1");
        restAction.model = model;

        request.setMethod("POST");
        restActionInvocation.setRestrictToGet("false");
        restActionInvocation.selectTarget();
        assertEquals(model, restActionInvocation.target);
        assertEquals(model.get(0), "Item1");
    }

    /**
     * Test the not modified status code.
     * @throws Exception
     */
    public void testResultNotModified() throws Exception {

        request.addHeader("If-None-Match", "123");
        request.setMethod("GET");

        RestAction restAction = (RestAction)restActionInvocation.getAction();
        List<String> model = new ArrayList<String>() {
            @Override
            public int hashCode() {
                return 123;
            }
        };
        model.add("Item");
        restAction.model = model;

        restActionInvocation.processResult();
        assertEquals(SC_NOT_MODIFIED, response.getStatus());

    }

    /**
     * Test the default error result.
     * @throws Exception
     */
    public void testDefaultErrorResult() throws Exception {

        // Exception
        Exception e = new Exception();
        restActionInvocation.getStack().set("exception", e);
        request.setMethod("GET");

        RestAction restAction = (RestAction)restActionInvocation.getAction();
        List<String> model = new ArrayList<String>();
        model.add("Item");
        restAction.model = model;

        restActionInvocation.setDefaultErrorResultName("default-error");
        ResultConfig resultConfig = new ResultConfig.Builder("default-error",
                "org.apache.struts2.result.HttpHeaderResult")
                .addParam("status", "123").build();
        ActionConfig actionConfig = new ActionConfig.Builder("org.apache.rest",
                "RestAction", "org.apache.rest.RestAction")
                .addResultConfig(resultConfig)
                .build();
        ((MockActionProxy)restActionInvocation.getProxy()).setConfig(actionConfig);

        restActionInvocation.processResult();
        assertEquals(123, response.getStatus());

    }

    public void testNoResult() throws Exception {

        RestAction restAction = (RestAction)restActionInvocation.getAction();
        List<String> model = new ArrayList<String>();
        model.add("Item");
        restAction.model = model;
        request.setMethod("GET");
        restActionInvocation.setResultCode("index");

        try {
            restActionInvocation.processResult();

            // ko
            assertFalse(true);

        } catch (ConfigurationException c) {
            // ok, no result
        }

    }

    /**
     * Test the global execution
     * @throws Exception
     */
    public void testInvoke() throws Exception {

        // Default index method return 'success'
        ((MockActionProxy)restActionInvocation.getProxy()).setMethod("index");

        // Define result 'success'
        ResultConfig resultConfig = new ResultConfig.Builder("success", "org.apache.struts2.result.HttpHeaderResult")
                .addParam("status", "123").build();
        ActionConfig actionConfig = new ActionConfig.Builder("org.apache.rest", "RestAction", "org.apache.rest.RestAction")
                .addResultConfig(resultConfig)
                .build();
        ((MockActionProxy)restActionInvocation.getProxy()).setConfig(actionConfig);

        request.setMethod("GET");

        restActionInvocation.setOgnlUtil(new OgnlUtil(
                new DefaultOgnlExpressionCacheFactory<>(String.valueOf(10_000), BASIC.toString()),
                new DefaultOgnlBeanInfoCacheFactory<>(String.valueOf(10_000), BASIC.toString()),
                new StrutsOgnlGuard()
        ));
        restActionInvocation.invoke();

        assertEquals(123, response.getStatus());
    }


    class RestActionInvocationTester extends RestActionInvocation {
        RestActionInvocationTester() {
            super(new HashMap<>(), true);
            List<InterceptorMapping> interceptorMappings = new ArrayList<>();
            MockInterceptor mockInterceptor = new MockInterceptor();
            mockInterceptor.setFoo("interceptor");
            mockInterceptor.setExpectedFoo("interceptor");
            interceptorMappings.add(new InterceptorMapping("interceptor", mockInterceptor));
            interceptors = interceptorMappings.iterator();
            MockActionProxy actionProxy = new MockActionProxy();
            ActionConfig actionConfig = new ActionConfig.Builder(
                    "org.apache.rest", "RestAction", "org.apache.rest.RestAction").build();
            actionProxy.setConfig(actionConfig);
            proxy = actionProxy;
            action = new RestAction();
            setMimeTypeHandlerSelector(new DefaultContentTypeHandlerManager());
            unknownHandlerManager = new DefaultUnknownHandlerManager();
            try {
                XWorkTestCaseHelper.setUp();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            invocationContext = ActionContext.getContext();
            container = ActionContext.getContext().getContainer();
            stack = ActionContext.getContext().getValueStack();
            objectFactory = container.getInstance(ObjectFactory.class);

        }

    }

    static class RestAction extends RestActionSupport implements ModelDriven<List<String>> {

        List<String> model;

        @Override
        public List<String> getModel() {
            return model;
        }

    }
}
