/*
 *
 *  * Copyright 2002-2007 the original author or authors.
 *  *
 *  * Licensed 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.suren.autotest.web.framework.settings;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriver.Options;
import org.openqa.selenium.html5.LocalStorage;
import org.openqa.selenium.html5.SessionStorage;
import org.openqa.selenium.html5.WebStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.suren.autotest.web.framework.annotation.AutoCookie;
import org.suren.autotest.web.framework.annotation.AutoExpect;
import org.suren.autotest.web.framework.annotation.AutoItem;
import org.suren.autotest.web.framework.annotation.AutoLocalStorage;
import org.suren.autotest.web.framework.annotation.AutoModule;
import org.suren.autotest.web.framework.annotation.AutoSessionStorage;
import org.suren.autotest.web.framework.log.LoggerConstants;

import com.surenpi.autotest.report.RecordReportWriter;
import com.surenpi.autotest.report.record.ExceptionRecord;
import com.surenpi.autotest.report.record.NormalRecord;
import com.surenpi.autotest.utils.PathUtil;
import com.surenpi.autotest.webui.Page;
import com.surenpi.autotest.webui.core.AutoTestException;
import com.surenpi.autotest.webui.ui.Text;

/**
 * 模块代理类。通过代理的方式来实现测试报告的输出。
 * @author <a href="http://surenpi.com">suren</a>
 */
public class AutoModuleProxy implements MethodInterceptor
{
    private static final Logger logger = LoggerFactory.getLogger(AutoModuleProxy.class);
    
    private Enhancer enhancer = new Enhancer();
    private Object target;
    private List<RecordReportWriter> recordReportWriters;
    private Phoenix util;

    public AutoModuleProxy(Object target, List<RecordReportWriter> recordReportWriters, Phoenix util)
    {
        this.target = target;
        this.recordReportWriters = recordReportWriters;
        this.util = util;
    }

	public Object getProxy()
    {
        Class<?> clazz = target.getClass();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        enhancer.setClassLoader(target.getClass().getClassLoader());
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable
    {
        long beginTime = System.currentTimeMillis();

        Object result = null;
        Class<?> superCls = obj.getClass().getSuperclass();
        AutoModule autoModule = superCls.getAnnotation(AutoModule.class);
        AutoExpect autoExpect = method.getAnnotation(AutoExpect.class);
        AutoSessionStorage autoSessionStorage = method.getAnnotation(AutoSessionStorage.class);
        AutoLocalStorage autoLocalStorage = method.getAnnotation(AutoLocalStorage.class);
        AutoCookie autoCookie = method.getAnnotation(AutoCookie.class);

        NormalRecord normalRecord = new NormalRecord();
        normalRecord.setBeginTime(beginTime);
        normalRecord.setClazzName(superCls.getName());
        normalRecord.setMethodName(method.getName());
        normalRecord.setModuleName(autoModule.name());
        normalRecord.setModuleDescription(autoModule.description());

        try
        {
            SessionStorageConfig sessionStorageConfig = new SessionStorageConfig();
            if(autoSessionStorage != null)
            {
                sessionStorageConfig.setAutoLoad(true);
                Class<? extends Page> accountClz = autoSessionStorage.pageClazz();
                String accountNameField = autoSessionStorage.sessionKey();

                Page page = util.getPage(accountClz);
                Field accountField = accountClz.getDeclaredField(accountNameField);

                accountField.setAccessible(true);
                Object value = accountField.get(page);

                if(value instanceof Text)
                {
                    String accountNameValue = ((Text) value).getValue();
                    sessionStorageConfig.setAccount(accountNameValue);
                    
                    Map<String, String> customMap = new HashMap<String, String>();
                    AutoItem[] overItems = autoSessionStorage.overItems();
                    if(overItems != null && overItems.length > 0)
                    {
                    	Arrays.asList(overItems).forEach((item) -> {
                    		customMap.put(item.key(), item.value());
                    	});
                    }

                    page.open();
                    if(loadSessionStorage(accountNameValue, customMap))
                    {
                        sessionStorageConfig.setAccount(accountNameValue);

                        if(autoSessionStorage.skipMethod())
                        {
                            sessionStorageConfig.setSkipLogin(true);
                        }
                    }
                }
                else
                {
                    throw new AutoTestException("Wrong account type in class: " + accountClz + ", and field : "
                            + accountNameField + ". It should be Text type.");
                }
            }
            
            LocalStorageConfig localStorageConfig = new LocalStorageConfig();
            if(autoLocalStorage != null)
            {
            	localStorageConfig.setAutoLoad(true);
                Class<? extends Page> accountClz = autoLocalStorage.pageClazz();
                String accountNameField = autoLocalStorage.sessionKey();

                Page page = util.getPage(accountClz);
                Field accountField = accountClz.getDeclaredField(accountNameField);

                accountField.setAccessible(true);
                Object value = accountField.get(page);

                if(value instanceof Text)
                {
                    String accountNameValue = ((Text) value).getValue();
                    localStorageConfig.setAccount(accountNameValue);
                    
                    Map<String, String> customMap = new HashMap<String, String>();
                    AutoItem[] overItems = autoLocalStorage.overItems();
                    if(overItems != null && overItems.length > 0)
                    {
                    	Arrays.asList(overItems).forEach((item) -> {
                    		customMap.put(item.key(), item.value());
                    	});
                    }

                    page.open();
                    if(loadLocalStorage(accountNameValue, customMap))
                    {
                    	localStorageConfig.setAccount(accountNameValue);

                        if(autoLocalStorage.skipMethod())
                        {
                        	localStorageConfig.setSkipLogin(true);
                        }
                    }
                }
                else
                {
                    throw new AutoTestException("Wrong account type in class: " + accountClz + ", and field : "
                            + accountNameField + ". It should be Text type.");
                }
            }
            
            //加载cookie信息
            boolean skipForCookie = false;
            if(autoCookie != null && PathUtil.isFile(autoCookie.fileName()))
            {
        		// 处理cookie
        		Options manage = util.getEngine().getDriver().manage();
        		File cookieFile = PathUtil.getFile(autoCookie.fileName());
        		
                Class<? extends Page> accountClz = autoCookie.pageClazz();
                String accountNameField = autoCookie.sessionKey();

                Page page = util.getPage(accountClz);
                Field accountField = accountClz.getDeclaredField(accountNameField);

                accountField.setAccessible(true);
                Object value = accountField.get(page);
                if(value instanceof Text)
                {
                    page.open();
                }
        		
    			try(ObjectInputStream input = new ObjectInputStream(new FileInputStream(cookieFile)))
    			{
    				Object cookiesObj = input.readObject();
    				if(cookiesObj != null && cookiesObj instanceof Set<?>)
    				{
    					@SuppressWarnings("unchecked")
						Set<Cookie> cookies =  (Set<Cookie>) cookiesObj;
    					cookies.parallelStream().forEach((cookie) -> {
    						manage.addCookie(cookie);
    					});
    				}
    				
                	skipForCookie = autoCookie.skipMethod();
    			}
    			catch (IOException e)
    			{
    			    logger.error("", e);
    			}
    			catch (ClassNotFoundException e)
    			{
                    logger.error("", e);
    			}
            }

            if(sessionStorageConfig.isSkipLogin() || localStorageConfig.isSkipLogin() || skipForCookie)
            {
                result = Void.TYPE;
            }
            else
            {
                result = methodProxy.invokeSuper(obj, args);
            }

            //保存sessionStorage
            if(sessionStorageConfig.isAutoLoad())
            {
                saveSessionStorage(sessionStorageConfig.getAccount());
            }

            if(localStorageConfig.isAutoLoad())
            {
                saveLocalStorage(localStorageConfig.getAccount());
            }
            
            //保存cookie信息
            if(autoCookie != null)
            {
    			Options manage = util.getEngine().getDriver().manage();
    			File cookieFile = PathUtil.getFile(autoCookie.fileName());
    			
				Set<Cookie> cookies = manage.getCookies();
				try(ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(cookieFile)))
				{
					output.writeObject(cookies);
				}
				catch (IOException e)
				{
                    logger.error("", e);
				}
            }

            normalRecord.setEndTime(System.currentTimeMillis());

            if(isNotExcludeMethod(method))
            {
                write(normalRecord);
            }
        }
        catch(Exception | AssertionError e)
        {
            boolean acceptException = exceptionHandle(autoExpect, e);

            normalRecord.setEndTime(System.currentTimeMillis());
            write(new ExceptionRecord(e, normalRecord));

            if(acceptException)
            {
                logger.error("", e);
            }
            else
            {
                throw e;
            }
        }

        return result;
    }

    /**
     * 保存sessionStorage信息
     * @param account
     */
    private void saveSessionStorage(String account)
    {
        WebDriver driver = util.getEngine().getDriver();
        if(driver instanceof WebStorage)
        {
            WebStorage webStorage = (WebStorage) driver;
            SessionStorage sessionStorage = webStorage.getSessionStorage();

            Properties pro = new Properties();
            for(String key : sessionStorage.keySet())
            {
                pro.setProperty(key, sessionStorage.getItem(key));
            }

            PathUtil.proStore(pro, "sessionStorage." + account);
        }
    }
    
    private void saveLocalStorage(String account)
    {
        WebDriver driver = util.getEngine().getDriver();
        if(driver instanceof WebStorage)
        {
            WebStorage webStorage = (WebStorage) driver;
            LocalStorage localStorage = webStorage.getLocalStorage();

            Properties pro = new Properties();
            for(String key : localStorage.keySet())
            {
                pro.setProperty(key, localStorage.getItem(key));
            }

            PathUtil.proStore(pro, "localStorage." + account);
        }
    }

    /**
     * 加载sessionStorage信息
     * @param accountNameValue
     * @return
     */
    private boolean loadSessionStorage(String accountNameValue, Map<String, String> customMap)
    {
        WebDriver webDriver = util.getEngine().getDriver();
        if(webDriver instanceof WebStorage)
        {
            WebStorage webStorage = (WebStorage) webDriver;
            SessionStorage sessionStorage = webStorage.getSessionStorage();

            Properties pro = new Properties();
            if(PathUtil.proLoad(pro, "sessionStorage." + accountNameValue))
            {
                if(pro.isEmpty())
                {
                    return false;
                }
                
                pro.putAll(customMap);

                pro.stringPropertyNames().parallelStream().forEach((key) -> {
                    sessionStorage.setItem(key, pro.getProperty(key));
                });

                return true;
            }
        }

        return false;
    }
    
    private boolean loadLocalStorage(String accountNameValue, Map<String, String> customMap)
    {
        WebDriver webDriver = util.getEngine().getDriver();
        if(webDriver instanceof WebStorage)
        {
            WebStorage webStorage = (WebStorage) webDriver;
            LocalStorage localStorage = webStorage.getLocalStorage();

            Properties pro = new Properties();
            if(PathUtil.proLoad(pro, "localStorage." + accountNameValue))
            {
                if(pro.isEmpty())
                {
                    return false;
                }
                
                pro.putAll(customMap);

                pro.stringPropertyNames().parallelStream().forEach((key) -> {
                	localStorage.setItem(key, pro.getProperty(key));
                });

                return true;
            }
        }

        return false;
    }

    /**
     * 根据注解配置,是否要对异常进行处理
     * @param autoExpect
     * @return
     */
    private boolean exceptionHandle(AutoExpect autoExpect, Throwable e)
    {
        if(autoExpect != null)
        {
            Class<?>[] acceptArray = autoExpect.accept();
            if(acceptArray != null && acceptArray.length > 0)
            {
                for(Class<?> clz : acceptArray)
                {
                     if(clz.equals(e.getClass()))
                    {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * 被排除的方法
     * @param method
     * @return
     */
    private boolean isNotExcludeMethod(Method method)
    {
        String name = method.getName();
        if("setEngine".equals(name))
        {
            return false;
        }

        if("setWebDriver".equals(name))
        {
            return false;
        }

        return true;
    }

    /**
     * 写入报告
     * @param record
     */
    private void write(NormalRecord record)
    {
        for(RecordReportWriter writer : recordReportWriters)
        {
            writer.write(record);
        }
    }

    /**
     * 写入报告
     * @param record
     */
    private void write(ExceptionRecord record)
    {
    	Map<Object, Object> config = util.getEngine().getEngineConfig();
    	
    	String root = (String) config.get(LoggerConstants.IMG_LOG_DIR);
    	String appDir = (String) config.get(LoggerConstants.APP_IDENTIFY);
    	String progressDir = (String) config.get(LoggerConstants.PROGRESS_IDENTIFY);
    	
    	File pngDir = new File(new File(root, appDir), progressDir);
    	if(pngDir.isDirectory())
    	{
    		File[] files = pngDir.listFiles();
    		if(files != null && files.length > 0)
    		{
        		List<File> attachFileList = new ArrayList<File>();
        		attachFileList.add(files[files.length - 1]);
        		record.setAttachFileList(attachFileList);
    		}
    	}
    	
        for(RecordReportWriter writer : recordReportWriters)
        {
            writer.write(record);
        }
    }
}