package com.theorchard.javaowsassetbulkscript.logic;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.servlet.http.HttpServletResponse;

import com.theorchard.javaowsassetbulkscript.models.ImportedProduct;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.theorchard.javaowsassetbulkscript.ImportedProductRepository;
import com.theorchard.javaowsassetbulkscript.LabelMappingRepository;
import com.theorchard.javaowsassetbulkscript.LabelStatusRepository;
import com.theorchard.javaowsassetbulkscript.UnknownOrchardUpcRepository;
import com.theorchard.javaowsassetbulkscript.api.ApiConnector;
import com.theorchard.javaowsassetbulkscript.api.ProductItem;
import com.theorchard.javaowsassetbulkscript.api.ProductListing;
import com.theorchard.javaowsassetbulkscript.api.ProductStatus;
import com.theorchard.javaowsassetbulkscript.models.LabelMapping;
import com.theorchard.javaowsassetbulkscript.models.LabelStatus;

@Controller
public class AssetImportController {

    @Autowired
    private LabelMappingRepository labelMappingRepository;

    @Autowired
    private LabelStatusRepository labelStatusRepository;

    @Autowired
    private ImportedProductRepository importedProductRepository;
    
    @Autowired
    private UnknownOrchardUpcRepository UnknownOrchardUpcRepository;
    
    @Autowired
    private ReportBuilder reportBuilder;

    @Autowired
    private AWSDetails finetunesAWSDetails;
    private ConcurrentLinkedQueue<ImportJob> jobQueue;

    private Thread importThread;

    private static int MAX_PARALLEL_JOBS = 15;
    public static boolean aborted = false;
    private static long lastLabelStarted = 0;

    @RequestMapping("/assets/errors")
    public ResponseEntity<List<LabelStatus>> getErrorLabels()
    {
        List<LabelStatus> labelStatuses = labelStatusRepository.findByStatus("Error");
        labelStatuses.addAll(labelStatusRepository.findByStatus("Exception"));

        ResponseEntity<List<LabelStatus>> response = new ResponseEntity(labelStatuses, HttpStatus.OK);
        return response;
    }

    @RequestMapping("/assets/abort")
    public ResponseEntity<String> abort()
    {
        aborted = true;
        importThread = null;

        return new ResponseEntity<>("Successfully aborted jobs", HttpStatus.OK);
    }

    @RequestMapping("/assets/check_timed_out")
    public ResponseEntity<String> checkTimedOut()
    {
        List<ImportedProduct> importedProducts = importedProductRepository.findByStatus("Error");

        for (ImportedProduct importedProduct : importedProducts) {
            if (importedProduct.getMessage().contains("timed out")) {
                try {
                    int productId = importedProduct.getProductId();
                    if (AutomaticAssetImporter.testProductIsDone(productId)) {

                        importedProduct.setStatus("Good");
                        importedProduct.setMessage("Assets verified");
                        importedProduct.setImportDate(new Date());

                        importedProductRepository.save(importedProduct);
                    }
                } catch (Exception e) {

                }
            }
        }

        return new ResponseEntity<>("Successfully  jobs", HttpStatus.OK);
    }

	@RequestMapping("/assets/report")
	public void downloadReport(HttpServletRequest request, HttpServletResponse response) {
		File file = reportBuilder.buildLabelStatusReport();
		downloadFile(file, response, "application/msexcel");
	}

	private void downloadFile(File file, HttpServletResponse response, String mimeType) {
		Path path = Paths.get(file.getAbsolutePath());
		if (!file.exists()) {
			System.err.println("File " + file.getAbsolutePath() + " not found");
			return;
		}
		response.setContentType(mimeType);
		response.addHeader("Content-Disposition", "attachment; filename=" + file.getName());
		try {
			Files.copy(path, response.getOutputStream());
			response.getOutputStream().flush();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	@RequestMapping("/assets/import/{label_id}")
    public ResponseEntity<String> importAssets(@PathVariable(value = "label_id", required=true) String labelId, @RequestParam(value="reupload_timedout", defaultValue = "true") Boolean reuploadTimedout, @RequestParam(value="update_assets", defaultValue = "false") Boolean updateAssets)
    {
        aborted = false;

        initMapping();

        if (jobQueue == null) {
            jobQueue = new ConcurrentLinkedQueue<>();
        }

        LabelMapping mapping = labelMappingRepository.findOne(labelId);

        if (mapping == null || mapping.getVendorId() == 0) {
            ResponseEntity<String> response = new ResponseEntity("Label ID "+labelId+" cannot be mapped to an Orchard Vendor ID", HttpStatus.BAD_REQUEST);
            return response;
        }

        LabelStatus labelStatus = labelStatusRepository.findOne(labelId);

        if (labelStatus != null && (labelStatus.getStatus().equals("Transferring") || labelStatus.equals("Queued"))) {
            return new ResponseEntity("Label already transferring. Check /assets/status/"+mapping.getLabelId()+" for Status", HttpStatus.OK);
        } else if (labelStatus == null) {
            labelStatus = new LabelStatus(labelId);
        }
        labelStatus.setStatus("Queued");

        labelStatus.setMessage("");
        labelStatusRepository.save(labelStatus);

        addImportJob(labelId, mapping.getVendorId(), reuploadTimedout, updateAssets);

        runImportJobQueue();

        ResponseEntity<String> response = new ResponseEntity("Job created. Check /assets/status/"+mapping.getLabelId()+" for Status", HttpStatus.OK);
        return response;
    }

    private synchronized  void addImportJob(String labelId, int vendorId, boolean reuploadTimedout, boolean updateAssets) {
        if (!containsJob(jobQueue, labelId)) {
            System.out.println("Adding new Job: "+labelId);
            jobQueue.add(new ImportJob(labelId, vendorId, reuploadTimedout, updateAssets));
            System.out.println("Now "+jobQueue.size()+" Jobs");
        }
    }

    private void runImportJobQueue() {
        if (importThread == null) {
            importThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!aborted) {
                        startAvailableJobs(jobQueue);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (aborted) {
                        stopAvailableJobs(jobQueue);
                    }
                }
            });
            importThread.start();
        }
    }

    private synchronized void stopAvailableJobs(ConcurrentLinkedQueue<ImportJob> jobQueue) {
        jobQueue.stream().filter(job -> !job.isActive()).forEach(job -> abortJob(job));
    }

    private void abortJob(ImportJob importJob) {
        String labelId = importJob.getLabelId();

        LabelStatus labelStatus = labelStatusRepository.findOne(labelId);
        labelStatus.setStatus("Aborted");
        labelStatusRepository.save(labelStatus);

        System.out.println("Removing "+importJob.getLabelId());
        jobQueue.remove(importJob);
    }

    private synchronized void startAvailableJobs(ConcurrentLinkedQueue<ImportJob> jobQueue) {
        long activeJobs = countActiveJobs(jobQueue);
        if (activeJobs < MAX_PARALLEL_JOBS && (lastLabelStarted == 0 || (System.currentTimeMillis() - lastLabelStarted) > 60000)) {
            ImportJob importJob = jobQueue.stream().filter(job -> !job.isActive()).findFirst().orElse(null);

            if (importJob != null) {
                importJob.setActive(true);

                String labelId = importJob.getLabelId();
                int vendorId = importJob.getVendorId();
                boolean reuploadTimedout = importJob.isReuploadTimedout();
                boolean updateAssets = importJob.isUpdateAssets();

                System.out.println(labelId+" started");

                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            lastLabelStarted = System.currentTimeMillis();
                            LabelStatus labelStatus = labelStatusRepository.findOne(labelId);
                            labelStatus.setStatus("Transferring");
                            labelStatusRepository.save(labelStatus);
                            AutomaticAssetImporter automaticAssetImporter = new AutomaticAssetImporter(importedProductRepository, UnknownOrchardUpcRepository, labelStatus);
                            if (automaticAssetImporter.transferLabel(finetunesAWSDetails, labelId, vendorId, reuploadTimedout, updateAssets)) {
                                labelStatus.setStatus("Done");
                            } else {
                                labelStatus.setStatus("Error");
                            }
                            labelStatus.setMessage(automaticAssetImporter.getStatus());
                            labelStatusRepository.save(labelStatus);
                        } catch (Exception e) {
                            e.printStackTrace();
                            LabelStatus labelStatus = labelStatusRepository.findOne(labelId);
                            labelStatus.setStatus("Exception");
                            labelStatus.setMessage(e.getMessage());
                            labelStatusRepository.save(labelStatus);
                        } finally {
                            System.out.println("Removing "+importJob.getLabelId());
                            jobQueue.remove(importJob);
                        }
                    }
                });

                thread.start();
            }
        } else {
            System.out.println("Jobqueue currently too busy: "+activeJobs+" active Jobs, last job started "+((System.currentTimeMillis()-lastLabelStarted)/1000)+" seconds ago");
        }
    }

    private synchronized boolean containsJob(Queue<ImportJob> queue, String labelId) {
        if (queue == null || queue.size() == 0) {
            return false;
        }
        return queue.stream().map(job -> job.getLabelId()).filter(id -> id.equals(labelId)).count() > 0;
    }

    private synchronized  long countActiveJobs(Queue<ImportJob> jobList) {
        return jobList.stream().filter(job -> job.isActive()).count();
    }

    @RequestMapping("/assets/status/{label_id}")
    public ResponseEntity<List<ProductStatus>> checkStatus(@PathVariable(value = "label_id", required=true) String labelId)
    {
        try {
            List<ProductStatus> productStatuses = new ArrayList<>();

            LabelMapping labelMapping = labelMappingRepository.findOne(labelId);

            int vendorId = 0;
            if (labelMapping != null) {
                vendorId = labelMapping.getVendorId();
            } else {
                vendorId = Integer.parseInt(labelId);
            }

            ProductListing productListing = ApiConnector.getProducts(vendorId);

            for (ProductItem productItem : productListing.getItems()) {
                productStatuses.add(ApiConnector.getProductStatus(productItem.getProduct_id()));
            }

            return new ResponseEntity<List<ProductStatus>>(productStatuses, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<List<ProductStatus>>(new ArrayList<>(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private void initMapping() {
        try {
            List<LabelMapping> dbMapping = labelMappingRepository.findAll();

            Map<String, String> mapping = LabelExcelLoader.loadLabelMapping(new File("src/main/resources/finetunes_to_orchard_labelids.xls"));

            if (dbMapping.isEmpty()) {
                for (String labelId : mapping.keySet()) {
                    LabelMapping labelMapping = new LabelMapping(labelId, Integer.parseInt(mapping.get(labelId)));
                    labelMappingRepository.save(labelMapping);
                }
            }

            lastLabelStarted = System.currentTimeMillis();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
