package rest

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/apierr"
	appErrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/backend/pkg/soql"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// --- Bulk Query API (P1) ---

// CreateQueryJob handles POST /jobs/query - creates async query job
// @Summary      Create Bulk Query Job
// @Description  Create a new bulk query job for async SOQL execution
// @Tags         bulk
// @Accept       json
// @Produce      json
// @Param        job body object true "Query Job Definition"
// @Success      201  {object}  map[string]interface{}
// @Failure      400  {object}  map[string]interface{}
// @Router       /jobs/query [post]
// @Security     BearerAuth
func (h *BulkHandler) CreateQueryJob(c *gin.Context) {
	user := MustGetUser(c)

	var req struct {
		Operation string `json:"operation"` // query or queryAll
		Query     string `json:"query"`     // SOQL query
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, err.Error()))
		return
	}

	if req.Query == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeMissingQuery, "Query is required"))
		return
	}

	// Default operation to "query"
	if req.Operation == "" {
		req.Operation = "query"
	}

	// Create query job with unique ID (750 prefix + timestamp + random suffix)
	job := &models.BulkJob{
		ID:              fmt.Sprintf("750%s%04d", time.Now().Format("20060102150405"), time.Now().Nanosecond()/100000),
		Operation:       models.OperationQuery,
		Object:          "",                            // Will be parsed from query
		State:           models.JobStateUploadComplete, // Query jobs skip upload phase
		JobType:         "V2Query",
		ConcurrencyMode: models.ConcurrencyParallel,
		ContentType:     "CSV",
		ColumnDelimiter: "COMMA",
		LineEnding:      "LF",
		ApiVersion:      63.0,
		CreatedById:     user.ID,
		CreatedDate:     time.Now(),
		SystemModstamp:  time.Now(),
	}

	// Store query in job metadata
	if err := h.bulkSvc.CreateJob(c.Request.Context(), job); err != nil {
		c.JSON(http.StatusInternalServerError, apierr.SFErrorResponse(apierr.SFCodeJobCreationFailed, err.Error()))
		return
	}

	// Store query for later execution (using job's ContentUrl field)
	job.ContentUrl = req.Query

	// Update job with query
	if err := h.bulkSvc.UpdateJob(c.Request.Context(), job); err != nil {
		log.Error().Err(err).Str("job_id", job.ID).Msg("Failed to store query in job")
	}

	// Start async processing
	go h.processQueryJob(job.ID, req.Query, user)

	c.JSON(http.StatusCreated, job)
}

// processQueryJob executes the query asynchronously and updates job status
func (h *BulkHandler) processQueryJob(jobID, query string, user *models.UserSession) {
	ctx := context.Background()
	job, err := h.bulkSvc.GetJob(ctx, jobID)
	if err != nil {
		log.Error().Err(err).Str("job_id", jobID).Msg("Failed to get job for processing")
		return
	}

	// Parse and validate the SOQL query
	parsedReq, err := soql.ParseSOQL(query)
	if err != nil {
		job.State = models.JobStateFailed
		job.ErrorMessage = fmt.Sprintf("Invalid SOQL: %s", err.Error())
		job.SystemModstamp = time.Now()
		if updateErr := h.bulkSvc.UpdateJob(ctx, job); updateErr != nil {
			log.Error().Err(updateErr).Str("job_id", jobID).Msg("Failed to update job state to failed")
		}
		return
	}

	// Execute the query using QueryService
	apiReq := api.QueryRequest{
		ObjectAPIName: parsedReq.ObjectAPIName,
		FilterExpr:    parsedReq.FilterExpr,
		SortField:     parsedReq.SortField,
		SortDirection: parsedReq.SortDirection,
		Limit:         parsedReq.Limit,
		Offset:        parsedReq.Offset,
	}

	result, err := h.querySvc.Query(ctx, apiReq, user)
	if err != nil {
		job.State = models.JobStateFailed
		job.ErrorMessage = fmt.Sprintf("Query execution failed: %s", err.Error())
		job.SystemModstamp = time.Now()
		if updateErr := h.bulkSvc.UpdateJob(ctx, job); updateErr != nil {
			log.Error().Err(updateErr).Str("job_id", jobID).Msg("Failed to update job state to failed")
		}
		return
	}

	// Update job with success status and record count
	job.State = models.JobStateJobComplete
	job.Object = parsedReq.ObjectAPIName // Set the object from parsed query
	job.SystemModstamp = time.Now()

	log.Info().
		Str("job_id", jobID).
		Str("object", parsedReq.ObjectAPIName).
		Int("records", len(result.Records)).
		Msg("Bulk query job completed successfully")

	if err := h.bulkSvc.UpdateJob(ctx, job); err != nil {
		log.Error().Err(err).Str("job_id", jobID).Msg("Failed to update job state")
	}
}

// GetQueryJob handles GET /jobs/query/:jobId
// @Summary      Get Query Job Status
// @Description  Get the status of a bulk query job
// @Tags         bulk
// @Produce      json
// @Param        jobId path string true "Job ID"
// @Success      200  {object}  map[string]interface{}
// @Failure      404  {object}  map[string]interface{}
// @Router       /jobs/query/{jobId} [get]
// @Security     BearerAuth
func (h *BulkHandler) GetQueryJob(c *gin.Context) {
	jobId := c.Param("jobId")
	user := MustGetUser(c)

	job, err := h.bulkSvc.GetJob(c.Request.Context(), jobId)
	if err != nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeJobNotFound, "Job not found"))
		return
	}

	if job.CreatedById != user.ID && !user.IsSystemAdmin {
		c.JSON(http.StatusForbidden, apierr.SFErrorResponse(apierr.SFCodeForbidden, "Insufficient access to this job"))
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"id":                     job.ID,
		"operation":              job.Operation,
		"object":                 job.Object,
		"createdById":            job.CreatedById,
		"createdDate":            job.CreatedDate.Format(time.RFC3339),
		"systemModstamp":         job.SystemModstamp.Format(time.RFC3339),
		"state":                  job.State,
		"jobType":                job.JobType,
		"numberRecordsProcessed": 0, // Would come from actual execution
	})
}

// GetQueryResults handles GET /jobs/query/:jobId/results
// @Summary      Get Query Results
// @Description  Get the results of a completed bulk query job
// @Tags         bulk
// @Produce      json
// @Param        jobId path string true "Job ID"
// @Param        maxRecords query int false "Max records to return"
// @Param        locator query string false "Pagination locator"
// @Success      200  {object}  map[string]interface{}
// @Failure      404  {object}  map[string]interface{}
// @Router       /jobs/query/{jobId}/results [get]
// @Security     BearerAuth
func (h *BulkHandler) GetQueryResults(c *gin.Context) {
	jobId := c.Param("jobId")
	user := MustGetUser(c)

	job, err := h.bulkSvc.GetJob(c.Request.Context(), jobId)
	if err != nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeJobNotFound, "Job not found"))
		return
	}

	if job.CreatedById != user.ID && !user.IsSystemAdmin {
		c.JSON(http.StatusForbidden, apierr.SFErrorResponse(apierr.SFCodeForbidden, "Insufficient access to this job"))
		return
	}

	if job.State != models.JobStateJobComplete {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse("JOB_NOT_COMPLETE", fmt.Sprintf("Job is in state %s, results not available", job.State)))
		return
	}

	// Parse optional query params
	var limit *int
	if maxRecordsStr := c.Query("maxRecords"); maxRecordsStr != "" {
		if n, err := strconv.Atoi(maxRecordsStr); err == nil && n > 0 {
			limit = &n
		}
	}

	// locator is essentially offset in this simplified version
	var offset *int
	if locatorStr := c.Query("locator"); locatorStr != "" {
		if n, err := strconv.Atoi(locatorStr); err == nil && n >= 0 {
			offset = &n
		}
	}

	// Execute the stored query with overrides
	query := job.ContentUrl
	if query == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeNoQuery, "No query stored for job"))
		return
	}

	soqlPkg, err := h.executeBulkQuery(c, query, user, limit, offset)
	if err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeQueryFailed, err.Error()))
		return
	}

	// Sforce-Locator: "null" means no more results (Salesforce convention)
	// Full cursor-based pagination is not yet implemented.
	c.Header("Sforce-Locator", "null")
	c.Header("Sforce-NumberOfRecords", fmt.Sprintf("%d", len(soqlPkg)))

	c.JSON(http.StatusOK, soqlPkg)
}

// executeBulkQuery executes a SOQL query and returns results using the wired QueryService
func (h *BulkHandler) executeBulkQuery(c *gin.Context, query string, user *models.UserSession, overrideLimit *int, overrideOffset *int) ([]map[string]interface{}, error) {
	// Validate query starts with SELECT
	if !strings.HasPrefix(strings.TrimSpace(strings.ToUpper(query)), "SELECT") {
		return nil, appErrors.NewValidationError("query", "invalid SOQL: must start with SELECT")
	}

	// Parse SOQL query using the soql parser
	parsedReq, err := soql.ParseSOQL(query)
	if err != nil {
		return nil, appErrors.NewInternalError("invalid SOQL", err)
	}

	// Apply overrides if provided
	limit := parsedReq.Limit
	if overrideLimit != nil {
		limit = *overrideLimit
	}

	offset := parsedReq.Offset
	if overrideOffset != nil {
		offset = *overrideOffset
	}

	// Convert to api.QueryRequest
	apiReq := api.QueryRequest{
		ObjectAPIName: parsedReq.ObjectAPIName,
		FilterExpr:    parsedReq.FilterExpr,
		SortField:     parsedReq.SortField,
		SortDirection: parsedReq.SortDirection,
		Limit:         limit,
		Offset:        offset,
	}

	// Execute query using the wired QueryService
	result, err := h.querySvc.Query(c.Request.Context(), apiReq, user)
	if err != nil {
		log.Error().Err(err).Str("query", query).Msg("Bulk query execution failed")
		return nil, err
	}

	// Convert models.SObject to map[string]interface{} for JSON response
	records := make([]map[string]interface{}, len(result.Records))
	for i, rec := range result.Records {
		records[i] = map[string]interface{}(rec)
	}

	return records, nil
}
