package queue

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/titansys/appy-builder/internal/config"
	"github.com/titansys/appy-builder/internal/models"
	"github.com/titansys/appy-builder/internal/platform"
)

// BuildCompositeKey creates a tenant-isolated key for hosted mode.
// Returns just buildID if purchaseCode is empty (non-hosted mode).
// This ensures builds from different Appy sites don't collide.
func BuildCompositeKey(purchaseCode, buildID string) string {
	purchaseCode = strings.TrimSpace(purchaseCode)
	if purchaseCode == "" {
		return buildID
	}
	return fmt.Sprintf("%s_%s", purchaseCode, buildID)
}

// AppyClient defines the interface for Appy API client operations
type AppyClient interface {
	GetPendingBuilds() ([]models.BuildRequest, error)
	GetPendingBuildsDynamic(siteURL, authKey string) ([]models.BuildRequest, error)
	UpdateBuildStatus(buildID string, status models.StatusUpdateRequest) error
	UpdateBuildStatusDynamic(buildID string, status models.StatusUpdateRequest, siteURL, authKey string) error
	DownloadKeystore(keystoreID string) ([]byte, error)
	DownloadKeystoreDynamic(keystoreID, siteURL, authKey string) ([]byte, error)
}

// Processor manages the build queue and workers
type Processor struct {
	config          *config.Config
	appyClient      AppyClient
	builder         platform.Builder
	logger          *logrus.Logger
	queue           chan models.BuildRequest
	statusMap       map[string]*models.BuildStatus
	statusMutex     sync.RWMutex
	pendingCount    int
	processingCount int
	completedCount  int
	failedCount     int
	countMutex      sync.RWMutex
}

// NewProcessor creates a new build queue processor
func NewProcessor(cfg *config.Config, appyClient AppyClient, builder platform.Builder, logger *logrus.Logger) *Processor {
	return &Processor{
		config:     cfg,
		appyClient: appyClient,
		builder:    builder,
		logger:     logger,
		queue:      make(chan models.BuildRequest, cfg.BuildBuffer),
		statusMap:  make(map[string]*models.BuildStatus),
	}
}

// Start starts the queue processor workers
func (p *Processor) Start() {
	p.logger.WithFields(logrus.Fields{
		"workers": p.config.BuildWorkers,
	}).Info("Starting build workers")

	for i := 0; i < p.config.BuildWorkers; i++ {
		go p.worker(i + 1)
	}
}

// worker processes builds from the queue
func (p *Processor) worker(id int) {
	p.logger.WithFields(logrus.Fields{
		"worker_id": id,
	}).Info("Worker started")

	for buildReq := range p.queue {
		// Use composite key for tenant isolation
		compositeKey := BuildCompositeKey(buildReq.PurchaseCode, buildReq.ID)

		p.logger.WithFields(logrus.Fields{
			"worker_id":     id,
			"build_id":      buildReq.ID,
			"composite_key": compositeKey,
			"platform":      buildReq.Platform,
			"build_type":    buildReq.BuildType,
		}).Info("Processing build")

		p.updateCounts(-1, 1, 0, 0) // pending--, processing++

		// Update internal status to processing (no notification to Appy - only final statuses)
		startTime := time.Now()
		status := &models.BuildStatus{
			BuildID:   buildReq.ID,
			Status:    "processing",
			Message:   "Build is being processed",
			StartedAt: startTime,
		}
		p.updateStatus(compositeKey, status)

		// Process the build
		artifactPath, buildLogs, err := p.builder.BuildWithLogs(buildReq)

		// Calculate duration
		status.EndedAt = time.Now()
		duration := int(status.EndedAt.Sub(startTime).Seconds())

		// Update status based on result
		status.BuildLogs = buildLogs
		if err != nil {
			p.logger.WithError(err).WithFields(logrus.Fields{
				"worker_id":     id,
				"build_id":      buildReq.ID,
				"composite_key": compositeKey,
				"duration":      time.Duration(duration) * time.Second,
			}).Error("Build failed")
			status.Status = "failed"
			status.Error = err.Error()
			status.Message = fmt.Sprintf("Build failed: %v", err)
			p.updateCounts(0, -1, 0, 1) // processing--, failed++
		} else {
			p.logger.WithFields(logrus.Fields{
				"worker_id":     id,
				"build_id":      buildReq.ID,
				"composite_key": compositeKey,
				"duration":      time.Duration(duration) * time.Second,
			}).Info("Build completed successfully")
			status.Status = "completed"
			status.ArtifactPath = artifactPath
			status.Message = "Build completed successfully"
			p.updateCounts(0, -1, 1, 0) // processing--, completed++
		}

		p.updateStatus(compositeKey, status)
		p.notifyAppyWithDuration(buildReq, *status, duration)
	}

	p.logger.WithFields(logrus.Fields{
		"worker_id": id,
	}).Info("Worker stopped")
}

// FetchAndQueueBuilds fetches pending builds from Appy and adds them to the queue
func (p *Processor) FetchAndQueueBuilds() error {
	builds, err := p.appyClient.GetPendingBuilds()
	if err != nil {
		return fmt.Errorf("failed to fetch pending builds: %w", err)
	}

	if len(builds) == 0 {
		p.logger.Debug("No pending builds found")
		return nil
	}

	for _, build := range builds {
		// Check if already in queue
		if _, exists := p.statusMap[build.ID]; exists {
			p.logger.WithFields(logrus.Fields{
				"build_id": build.ID,
			}).Debug("Build already in queue, skipping")
			continue
		}

		// Add to queue
		p.queue <- build
		p.updateCounts(1, 0, 0, 0) // pending++

		// Initialize internal status (no notification to Appy - only final statuses)
		status := &models.BuildStatus{
			BuildID: build.ID,
			Status:  "pending",
			Message: "Build queued for processing",
		}
		p.updateStatus(build.ID, status)

		p.logger.WithFields(logrus.Fields{
			"build_id":   build.ID,
			"build_type": build.BuildType,
		}).Info("Build added to queue")
	}

	return nil
}

// FetchAndQueueBuildsDynamic fetches pending builds from a specific Appy instance and adds them to the queue
func (p *Processor) FetchAndQueueBuildsDynamic(siteURL, purchaseCode string) error {
	builds, err := p.appyClient.GetPendingBuildsDynamic(siteURL, purchaseCode)
	if err != nil {
		return fmt.Errorf("failed to fetch pending builds: %w", err)
	}

	if len(builds) == 0 {
		p.logger.Debug("No pending builds found")
		return nil
	}

	for _, build := range builds {
		// Attach callback context
		build.SiteURL = siteURL
		build.PurchaseCode = purchaseCode

		// Use composite key for tenant isolation in hosted mode
		compositeKey := BuildCompositeKey(purchaseCode, build.ID)

		// Check if already in queue (using composite key)
		if _, exists := p.statusMap[compositeKey]; exists {
			p.logger.WithFields(logrus.Fields{
				"build_id":      build.ID,
				"composite_key": compositeKey,
			}).Debug("Build already in queue, skipping")
			continue
		}

		// Add to queue
		p.queue <- build
		p.updateCounts(1, 0, 0, 0) // pending++

		// Initialize internal status (no notification to Appy - only final statuses)
		status := &models.BuildStatus{
			BuildID: build.ID,
			Status:  "pending",
			Message: "Build queued for processing",
		}
		p.updateStatus(compositeKey, status)

		p.logger.WithFields(logrus.Fields{
			"build_id":      build.ID,
			"composite_key": compositeKey,
			"build_type":    build.BuildType,
			"site_url":      siteURL,
		}).Info("Build added to queue")
	}

	return nil
}

// updateStatus updates the status of a build
func (p *Processor) updateStatus(buildID string, status *models.BuildStatus) {
	p.statusMutex.Lock()
	defer p.statusMutex.Unlock()
	p.statusMap[buildID] = status
}

// GetBuildStatus returns the status of a build
func (p *Processor) GetBuildStatus(buildID string) (*models.BuildStatus, bool) {
	p.statusMutex.RLock()
	defer p.statusMutex.RUnlock()
	status, exists := p.statusMap[buildID]
	return status, exists
}

// notifyAppy sends status update to Appy server
func (p *Processor) notifyAppy(buildID string, status models.BuildStatus) {
	update := models.StatusUpdateRequest{
		Status:       status.Status,
		Message:      status.Message,
		ArtifactPath: status.ArtifactPath,
		Error:        status.Error,
	}

	if err := p.appyClient.UpdateBuildStatus(buildID, update); err != nil {
		p.logger.WithError(err).WithFields(logrus.Fields{
			"build_id": buildID,
		}).Warn("Failed to notify Appy server")
	}
}

// notifyAppyWithDuration sends status update with build logs and duration to Appy server
func (p *Processor) notifyAppyWithDuration(buildReq models.BuildRequest, status models.BuildStatus, duration int) {
	update := models.StatusUpdateRequest{
		Status:        status.Status,
		Message:       status.Message,
		ArtifactPath:  status.ArtifactPath,
		Error:         status.Error,
		BuildLogs:     status.BuildLogs,
		BuildDuration: duration,
	}

	var err error
	if buildReq.SiteURL != "" && buildReq.PurchaseCode != "" {
		// Use per-request callback context
		err = p.appyClient.UpdateBuildStatusDynamic(buildReq.ID, update, buildReq.SiteURL, buildReq.PurchaseCode)
	} else {
		// Use config values
		err = p.appyClient.UpdateBuildStatus(buildReq.ID, update)
	}

	if err != nil {
		p.logger.WithError(err).WithFields(logrus.Fields{
			"build_id": buildReq.ID,
		}).Warn("Failed to notify Appy server")
	}
}

// updateCounts updates the build counts
func (p *Processor) updateCounts(pendingDelta, processingDelta, completedDelta, failedDelta int) {
	p.countMutex.Lock()
	defer p.countMutex.Unlock()
	p.pendingCount += pendingDelta
	p.processingCount += processingDelta
	p.completedCount += completedDelta
	p.failedCount += failedDelta
}

// PendingCount returns the number of pending builds
func (p *Processor) PendingCount() int {
	p.countMutex.RLock()
	defer p.countMutex.RUnlock()
	return p.pendingCount
}

// ProcessingCount returns the number of builds being processed
func (p *Processor) ProcessingCount() int {
	p.countMutex.RLock()
	defer p.countMutex.RUnlock()
	return p.processingCount
}

// CompletedCount returns the number of completed builds
func (p *Processor) CompletedCount() int {
	p.countMutex.RLock()
	defer p.countMutex.RUnlock()
	return p.completedCount
}

// FailedCount returns the number of failed builds
func (p *Processor) FailedCount() int {
	p.countMutex.RLock()
	defer p.countMutex.RUnlock()
	return p.failedCount
}
