/* 
 * Serposcope - SEO rank checker https://serposcope.serphacker.com/
 * 
 * Copyright (c) 2016 SERP Hacker
 * @author Pierre Nogues <support@serphacker.com>
 * @license https://opensource.org/licenses/MIT MIT License
 */
package serposcope.controllers.google;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.inject.Inject;
import ninja.Result;
import ninja.Results;

import com.google.inject.Singleton;
import com.serphacker.serposcope.db.base.BaseDB;
import static com.serphacker.serposcope.db.base.RunDB.STATUSES_DONE;
import com.serphacker.serposcope.db.google.GoogleDB;
import com.serphacker.serposcope.models.base.Config;
import com.serphacker.serposcope.models.base.Group;
import com.serphacker.serposcope.models.base.Group.Module;
import com.serphacker.serposcope.models.base.Run;
import com.serphacker.serposcope.models.google.GoogleBest;
import com.serphacker.serposcope.models.google.GoogleRank;
import static com.serphacker.serposcope.models.google.GoogleRank.UNRANKED;
import com.serphacker.serposcope.models.google.GoogleSettings;
import com.serphacker.serposcope.models.google.GoogleSearch;
import com.serphacker.serposcope.models.google.GoogleSerp;
import com.serphacker.serposcope.models.google.GoogleSerpEntry;
import com.serphacker.serposcope.models.google.GoogleTarget;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ninja.Context;
import ninja.Router;
import ninja.i18n.Messages;
import ninja.params.Param;
import ninja.params.PathParam;
import org.apache.commons.lang3.StringEscapeUtils;


@Singleton
public class GoogleSearchController extends GoogleController {
    
    @Inject
    GoogleDB googleDB;
    
    @Inject
    BaseDB baseDB;
    
    @Inject
    Router router;
    
    @Inject
    Messages msg;
    
    @Inject
    ObjectMapper objectMapper;    
    
    public Result search(Context context, 
        @PathParam("searchId") Integer searchId,
        @Param("startDate") String startDateStr,
        @Param("endDate") String endDateStr
    ){
        GoogleSearch search = getSearch(context, searchId);
        Group group = context.getAttribute("group", Group.class);
        
        if(search == null){
            context.getFlashScope().error("error.invalidSearch");
            return Results.redirect(router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
        }
        
        Run minRun = baseDB.run.findFirst(Module.GOOGLE, STATUSES_DONE, null);
        Run maxRun = baseDB.run.findLast(Module.GOOGLE, STATUSES_DONE, null);
        if(maxRun == null || minRun == null){
            return Results.ok()
                .render("search", search);
        }
        
        LocalDate minDay = minRun.getDay();
        LocalDate maxDay = maxRun.getDay();
        
        LocalDate startDate = null;
        if(startDateStr != null){
            try {startDate = LocalDate.parse(startDateStr);} catch(Exception ex){}
        }
        LocalDate endDate = null;
        if(endDateStr != null){
            try {endDate = LocalDate.parse(endDateStr);} catch(Exception ex){}
        }
        
        if(startDate == null || endDate == null || endDate.isBefore(startDate)){
            startDate = maxDay.minusDays(30);
            endDate = maxDay;
        }
        
        Run firstRun = baseDB.run.findFirst(Module.GOOGLE, STATUSES_DONE, startDate);
        Run lastRun = baseDB.run.findLast(Module.GOOGLE, STATUSES_DONE, endDate);
        
        if(firstRun == null || lastRun == null || firstRun.getDay().isAfter(lastRun.getDay())){
            return Results.ok()
                .render("f_warning", msg.get("error.noDataForThisPeriod", context, Optional.absent()).or(""))
                .render("startDate", startDate)
                .render("endDate", endDate)
                .render("minDate", minDay)
                .render("maxDate", maxDay)                        
                .render("search", search);            
        }
        
        startDate = firstRun.getDay();
        endDate = lastRun.getDay();
        
        
        String jsonEvents = null;
        try {
            jsonEvents = objectMapper.writeValueAsString(baseDB.event.list(group, startDate, endDate));
        } catch(JsonProcessingException ex){
            jsonEvents = "[]";
        }
        
        GoogleSerp lastSerp = googleDB.serp.get(lastRun.getId(), search.getId());
        
        List<GoogleTarget> targets = getTargets(context);
        
        Map<Integer, GoogleBest> bestRankings = new HashMap<>();
        for (GoogleTarget target : targets) {
            GoogleBest best = googleDB.rank.getBest(target.getGroupId(), target.getId(), search.getId());
            if(best != null){
                bestRankings.put(best.getGoogleTargetId(), best);
            }
        }
        
        
        
        String jsonRanks = getJsonRanks(group, targets, firstRun, lastRun, searchId);
        Config config = baseDB.config.getConfig();
        
        return Results.ok()
            .render("displayMode", config.getDisplayGoogleSearch())
            .render("events", jsonEvents)
            .render("targets", targets)
            .render("chart", jsonRanks)
            .render("search", search)
            .render("serp", lastSerp)
            .render("startDate", startDate)
            .render("endDate", endDate)
            .render("minDate", minDay)
            .render("maxDate", maxDay)
            .render("bestRankings", bestRankings)
            ;
    }

    
    protected String getJsonRanks(Group group, List<GoogleTarget> targets,Run firstRun, Run lastRun, int searchId){
        
        StringBuilder builder = new StringBuilder("{\"targets\":[");
        for (GoogleTarget target : targets) {
            builder.append("{\"id\":").append(target.getId())
            .append(",\"name\":\"").append(StringEscapeUtils.escapeJson(target.getName())).append("\"},");
        }
        if(builder.charAt(builder.length()-1) == ','){
            builder.setCharAt(builder.length()-1, ']');
        } else {
            builder.append(']');
        }
        builder.append(",\"ranks\":[");
        
        final int[] maxRank = new int[1];
        
        googleDB.serp.stream(firstRun.getId(), lastRun.getId(), searchId, (GoogleSerp serp) -> {
            
            builder.append('[').append(serp.getRunDay().toEpochSecond(ZoneOffset.UTC)*1000l).append(',');
            
            // calendar
            builder.append("null").append(",");
            
            for (GoogleTarget target : targets) {
                int position = UNRANKED;
                for (int i = 0; i < serp.getEntries().size(); i++) {
                    if(target.match(serp.getEntries().get(i).getUrl())){
                        position = i + 1;
                        break;
                    }
                }
                
                builder.append(position == UNRANKED ? "null" : position).append(',');
                if(position != UNRANKED && position > maxRank[0]){
                    maxRank[0] = position;
                }
            }
            
            if(builder.charAt(builder.length()-1) == ','){
                builder.setCharAt(builder.length()-1, ']');
            }
            builder.append(',');
        });
        if(builder.charAt(builder.length()-1) == ','){
            builder.setCharAt(builder.length()-1, ']');
        } else {
            builder.append(']');
        }
        
        builder.append(",\"maxRank\":").append(maxRank[0]);
        builder.append("}");
        
        return builder.toString();
    }
    
    public Result urlRanks(
        Context context,
        @PathParam("searchId") Integer searchId,
        @Param("url") String url,
        @Param("startDate") String startDateStr,
        @Param("endDate") String endDateStr        
    ){
        Group group = (Group)context.getAttribute("group");
        
        GoogleSearch search = getSearch(context, searchId);
        if(search == null){
            context.getFlashScope().error("error.invalidSearch");
            return Results.redirect(router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
        }        
        
        LocalDate startDate = null;
        if(startDateStr != null){
            try {startDate = LocalDate.parse(startDateStr);} catch(Exception ex){}
        }
        LocalDate endDate = null;
        if(endDateStr != null){
            try {endDate = LocalDate.parse(endDateStr);} catch(Exception ex){}
        }        
        
        Run firstRun = baseDB.run.findFirst(Module.GOOGLE, STATUSES_DONE, startDate);
        Run lastRun = baseDB.run.findLast(Module.GOOGLE, STATUSES_DONE, endDate);
        
        if(url == null || firstRun == null || lastRun == null){
            return Results.badRequest().text();
        }
        
        StringBuilder builder = new StringBuilder("{");
        googleDB.serp.stream(firstRun.getId(), lastRun.getId(), search.getId(), (GoogleSerp t) -> {
            int position = 0;
            for (int i = 0; i < t.getEntries().size(); i++) {
                if(t.getEntries().get(i).getUrl().equals(url)){
                    position = i + 1;
                    break;
                }
            }
            
            builder
                .append("\"")
                .append(t.getRunDay().toEpochSecond(ZoneOffset.UTC)*1000l)
                .append("\":")
                .append(position)
                .append(",");
        });
        
        if(builder.charAt(builder.length()-1) == ','){
            builder.setCharAt(builder.length()-1, '}');
        } else {
            builder.append('}');
        }
        
        return Results.ok()
            .text()
            .render(builder.toString());
    }
    
    public Result exportSerp(Context context, 
        @PathParam("searchId") Integer searchId,
        @Param("date") String pdate
    ){
        GoogleSerp serp=null;
        LocalDate date=null;
        try {date = LocalDate.parse(pdate);}catch(Exception ex){}
        if(date != null){
            List<Run> runs = baseDB.run.findByDay(Module.GOOGLE, date);
            if(!runs.isEmpty()){
                GoogleSearch search = getSearch(context, searchId);
                if(search != null){
                    serp = googleDB.serp.get(runs.get(0).getId(), search.getId());
                }
            }
        }
        
        if(serp == null){
            return Results.ok().text().renderRaw("SERP not found");
        }
        
        boolean exportRank = context.getParameter("rank") != null;
        boolean exportD1 = context.getParameter("d1") != null;
        boolean exportD7 = context.getParameter("d7") != null;
        boolean exportD30 = context.getParameter("d30") != null;
        boolean exportD90 = context.getParameter("d90") != null;
        
        int position = 0;
        StringBuilder builder = new StringBuilder();
        for (GoogleSerpEntry entry : serp.getEntries()) {
            ++position;
            if(exportRank){
                builder.append(position).append(",");
            }
            builder.append(StringEscapeUtils.escapeCsv(entry.getUrl())).append(",");
            if(exportD1){
                Short rank = entry.getMap().getOrDefault((short)1, (short)GoogleRank.UNRANKED);
                builder.append(rank != GoogleRank.UNRANKED ? rank.intValue() : "").append(",");
            }
            if(exportD7){
                Short rank = entry.getMap().getOrDefault((short)7, (short)GoogleRank.UNRANKED);
                builder.append(rank != GoogleRank.UNRANKED ? rank.intValue() : "").append(",");
            }
            if(exportD30){
                Short rank = entry.getMap().getOrDefault((short)30, (short)GoogleRank.UNRANKED);
                builder.append(rank != GoogleRank.UNRANKED ? rank.intValue() : "").append(",");
            }
            if(exportD90){
                Short rank = entry.getMap().getOrDefault((short)90, (short)GoogleRank.UNRANKED);
                builder.append(rank != GoogleRank.UNRANKED ? rank.intValue() : "").append(",");
            }
            if(builder.length() > 0){
                builder.setCharAt(builder.length()-1, '\n');
            }
        }
        
        return Results.text()
            .addHeader("Content-Disposition", "attachment; filename=\"" + serp.getRunDay().toLocalDate() + ".csv\"")
            .renderRaw(builder.toString());
    }    
    
    
}