/**
 * Copyright (c) 2025 SoftCorporation LLC. All rights reserved.
 *
 * The Software License, Version 1.0
 *
 * SoftCorporation LLC. grants you ("Licensee") a non-exclusive, royalty free,
 * license to use, modify and redistribute this software in source and binary
 * code form, provided that the following conditions are met:
 *
 * 1. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        SoftCorporation LLC. (http://www.softcorporation.com)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 2. This software and SoftCorporation name must not be used to
 *    promote products derived from the software without prior
 *    written permission. For written permission, please contact
 *    info@softcorporation.com.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 * IN NO EVENT SHALL THE SOFTCORPORATION BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION).
 *
 */
package com.softcorporation.sandi.index.controller;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.softcorporation.sandi.base.model.Client;
import com.softcorporation.sandi.base.service.ClientService;
import com.softcorporation.sandi.base.util.Constants;
import com.softcorporation.sandi.index.model.IndexingJob;
import com.softcorporation.sandi.index.model.ScheduleRequest;
import com.softcorporation.sandi.index.model.ScheduleResponse;
import com.softcorporation.sandi.index.service.IndexingJobScheduler;

@RestController
@RequestMapping("/schedule")
public class ScheduleController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleController.class);

    @Autowired
    private ClientService clientService;

    @Autowired
    private IndexingJobScheduler indexingJobScheduler;

    @PostMapping("/index")
    public ResponseEntity<ScheduleResponse> scheduleIndexing(@RequestBody ScheduleRequest request) {
        LOGGER.info("Received schedule indexing request: {}", request);

    	// Validate request
        ScheduleResponse validationError = validateRequest(request);
        if (validationError != null) {
        	return ResponseEntity.badRequest().body(validationError);
        }
        
        String requestId = request.getRequestId();
        try {
            // Validate client exists
            Client client = clientService.getClient(request.getClientId());
            if (client == null) {
                LOGGER.warn("Client not found: {}", request.getClientId());
                ScheduleResponse response = new ScheduleResponse(requestId, Constants.STATUS_ERROR, 
                    "Client not found: " + request.getClientId());
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
            }

            // Validate scheduled time is in the future
            LocalDateTime now = LocalDateTime.now();
            if (request.getScheduledTime().isBefore(now)) {
                LOGGER.warn("Scheduled time is in the past: {}", request.getScheduledTime());
                ScheduleResponse response = new ScheduleResponse(requestId, Constants.STATUS_ERROR, 
                    "Scheduled time must be in the future");
                response.setScheduledTime(request.getScheduledTime());
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
            }

            // Validate directory
            if (request.getDirectory() == null || request.getDirectory().trim().isEmpty()) {
                LOGGER.warn("Directory not provided in request: {}", requestId);
                ScheduleResponse response = new ScheduleResponse(requestId, Constants.STATUS_ERROR, 
                    "Directory is required");
                response.setScheduledTime(request.getScheduledTime());
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
            }

            // make jobId
            String jobId = request.getJobId();
            if (jobId == null) {
            	jobId = requestId;
            }
            
            // Create and schedule the indexing job
            IndexingJob job = new IndexingJob(jobId, request.getClientId(), request.getJobType(), request.getCron(),
            		request.getDirectory(), request.getFileExtensions(), request.getForceReindexing(),
            		request.getScheduledTime(), request.getCreatedTime());
            
            boolean success = indexingJobScheduler.addJob(job);
            if (success) {
                LOGGER.info("Successfully created indexing job: {}", jobId);
                ScheduleResponse response = new ScheduleResponse(requestId, "SCHEDULED",
                    "Indexing job created and scheduled successfully");
                response.setScheduledTime(request.getScheduledTime());
                response.setJobId(jobId);
                response.setCreatedTime(request.getCreatedTime());
                return ResponseEntity.ok(response);
            } else {
                LOGGER.error("Failed to create indexing job: {}", requestId);
                ScheduleResponse response = new ScheduleResponse(requestId, Constants.STATUS_ERROR, 
                    "Failed to create indexing job");
                response.setScheduledTime(request.getScheduledTime());
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
            }

        } catch (Exception e) {
            LOGGER.error("Error processing schedule request {}: {}", requestId, e.getMessage(), e);
            ScheduleResponse response = new ScheduleResponse(requestId, Constants.STATUS_ERROR, 
                "Internal server error: " + e.getMessage());
            response.setScheduledTime(request.getScheduledTime());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    @GetMapping("/jobs")
    public ResponseEntity<List<IndexingJob>> getAllJobs() {
        try { 
            List<IndexingJob> jobs = indexingJobScheduler.getAllJobs();
            return ResponseEntity.ok(jobs);
        } catch (Exception e) {
            LOGGER.error("Error retrieving jobs: {}", e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    @GetMapping("/jobs/{jobId}")
    public ResponseEntity<IndexingJob> getJob(@PathVariable String jobId) {
        try {
            IndexingJob job = indexingJobScheduler.getJob(jobId);
            if (job != null) {
                return ResponseEntity.ok(job);
            } else {
                return ResponseEntity.notFound().build();
            }
        } catch (Exception e) {
            LOGGER.error("Error retrieving job {}: {}", jobId, e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    @GetMapping("/jobs/status/{status}")
    public ResponseEntity<List<IndexingJob>> getJobsByStatus(@PathVariable String status) {
        try {
            IndexingJob.JobStatus jobStatus = IndexingJob.JobStatus.valueOf(status.toUpperCase());
            List<IndexingJob> jobs = indexingJobScheduler.getJobsByStatus(jobStatus);
            return ResponseEntity.ok(jobs);
        } catch (IllegalArgumentException e) {
            LOGGER.warn("Invalid status parameter: {}", status);
            return ResponseEntity.badRequest().build();
        } catch (Exception e) {
            LOGGER.error("Error retrieving jobs by status {}: {}", status, e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    @PostMapping("/jobs/{jobId}/cancel")
    public ResponseEntity<ScheduleResponse> cancelJob(@PathVariable String jobId) {
        try {
            boolean cancelled = indexingJobScheduler.cancelJob(jobId);
            
            if (cancelled) {
                LOGGER.info("Successfully cancelled job: {}", jobId);
                ScheduleResponse response = new ScheduleResponse(jobId, "CANCELLED", 
                    "Job cancelled successfully");
                return ResponseEntity.ok(response);
            } else {
                LOGGER.warn("Job not found or cannot be cancelled: {}", jobId);
                ScheduleResponse response = new ScheduleResponse(jobId, Constants.STATUS_ERROR, 
                    "Job not found or cannot be cancelled");
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
            }
        } catch (Exception e) {
            LOGGER.error("Error cancelling job {}: {}", jobId, e.getMessage(), e);
            ScheduleResponse response = new ScheduleResponse(jobId, Constants.STATUS_ERROR, 
                "Internal server error: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    @GetMapping("/stats")
    public ResponseEntity<Object> getJobStatistics() {
        try {
            return ResponseEntity.ok(new Object() {
                @SuppressWarnings("unused")
				public final int scheduled = indexingJobScheduler.getScheduledJobCount();
                @SuppressWarnings("unused")
                public final int running = indexingJobScheduler.getRunningJobCount();
                @SuppressWarnings("unused")
                public final int completed = indexingJobScheduler.getCompletedJobCount();
            });
        } catch (Exception e) {
            LOGGER.error("Error retrieving job statistics: {}", e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    /**
     * Validates the request
     * @param request the request to validate
     * @return ScheduleResponse with error if validation fails, null if valid
     */
    private ScheduleResponse validateRequest(ScheduleRequest request) {
        if (request.getRequestId() == null || request.getRequestId().trim().isEmpty()) {
            LOGGER.warn("Index request missing requestId");
            return new ScheduleResponse(Constants.MISSING, Constants.STATUS_ERROR, "RequestId is required");
        }
        
        if (request.getClientId() == null || request.getClientId().trim().isEmpty()) {
            LOGGER.warn("Index request missing clientId");
            return new ScheduleResponse(request.getRequestId(), Constants.STATUS_ERROR, "ClientId is required");
        }
        
        if (request.getJobType() == null || request.getJobType().trim().isEmpty()) {
            LOGGER.warn("Index request missing job type");
            return new ScheduleResponse(request.getRequestId(), Constants.STATUS_ERROR, "Job type is missing");
        }
        
        if (!(request.getJobType().equals(Constants.JOB_TYPE_JSON) ||
              request.getJobType().equals(Constants.JOB_TYPE_JSONL) ||
              request.getJobType().equals(Constants.JOB_TYPE_TXT) ||
              request.getJobType().equals(Constants.JOB_TYPE_TXTL) ||
              request.getJobType().equals(Constants.JOB_TYPE_SITE) ||
              request.getJobType().equals(Constants.JOB_TYPE_SITEMAP) ||
              request.getJobType().equals(Constants.JOB_TYPE_JSONMAP) ||
              request.getJobType().equals(Constants.JOB_TYPE_EXCEL)
          	  )) {
            LOGGER.warn("Index request has invalid job type");
            return new ScheduleResponse(request.getRequestId(), Constants.STATUS_ERROR, "Invalid job type");
        }
        
        // temp empty start indexing time (starts in 5 sec) 
        if (request.getScheduledTime() == null) {
        	request.setScheduledTime(LocalDateTime.now().plus(5, ChronoUnit.SECONDS));
        }
        
        if (request.getScheduledTime() == null || request.getClientId().trim().isEmpty()) {
            LOGGER.warn("Index request missing scheduled time");
            return new ScheduleResponse(request.getRequestId(), Constants.STATUS_ERROR, "ScheduledTime is required");
        }
        
        return null;
    }

}