package worker

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"sync"
	"time"

	"webhook-service/config"
	"webhook-service/models"
)

type Pool struct {
	config   *config.Config
	db       *sql.DB
	jobs     chan int64
	wg       sync.WaitGroup
	stopChan chan struct{}
	renewMu  sync.Mutex
}

func NewPool(cfg *config.Config, db *sql.DB) *Pool {
	return &Pool{
		config:   cfg,
		db:       db,
		jobs:     make(chan int64, 1000),
		stopChan: make(chan struct{}),
	}
}

func (p *Pool) Start() {
	for i := 0; i < p.config.WorkerCount; i++ {
		p.wg.Add(1)
		go p.worker(i)
	}
}

func (p *Pool) Stop() {
	close(p.stopChan)
	p.wg.Wait()
}

func (p *Pool) Submit(queueID int64) {
	select {
	case p.jobs <- queueID:
		// submitted
	case <-time.After(30 * time.Second):
		// Jangan drop: job sudah di DB, akan di-pick saat startup/retry
		log.Printf("⚠️ [Pool] Job %d not submitted within 30s (pool busy); remains pending in DB", queueID)
	}
}

func (p *Pool) worker(id int) {
	defer p.wg.Done()
	log.Printf("👷 Worker %d started", id)

	for {
		select {
		case <-p.stopChan:
			log.Printf("👷 Worker %d stopped", id)
			return
		case queueID := <-p.jobs:
			p.processJob(queueID)
		}
	}
}

func (p *Pool) processJob(queueID int64) {
	startTime := time.Now()

	var queue models.WebhookQueue
	err := p.db.QueryRow(`
		SELECT id, type, database_id, payload, attempts
		FROM webhook_queue
		WHERE id = ?
	`, queueID).Scan(&queue.ID, &queue.Type, &queue.DatabaseID, &queue.Payload, &queue.Attempts)

	if err != nil {
		log.Printf("❌ [Queue %d] Failed to fetch: %v", queueID, err)
		return
	}

	p.db.Exec(`UPDATE webhook_queue SET status = 'processing', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, queueID)

	// Check Accurate Webhook Subscription Renewal
	p.checkWebhookActivePeriod()

	var payload interface{}
	if err := json.Unmarshal([]byte(queue.Payload), &payload); err != nil {
		p.markFailed(queueID, fmt.Sprintf("Invalid JSON: %v", err))
		return
	}

	// Check if payload is Draft (DFT) - skip forward to shared hosting
	if p.isDraftPayload(payload, queue.Type) {
		log.Printf("⏭️ [Queue %d] Skipping draft %s (DFT) - not forwarding to shared hosting", queueID, queue.Type)
		p.db.Exec(`UPDATE webhook_queue SET status = 'completed', error = 'skipped_draft', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, queueID)
		duration := time.Since(startTime).Milliseconds()
		log.Printf("✅ [Queue %d] Skipped draft %s (%dms)", queueID, queue.Type, duration)
		return
	}

	// Forward to Shared Hosting
	log.Printf("🔄 [Queue %d] Forwarding %s to shared hosting...", queueID, queue.Type)
	err = p.forwardToSharedHosting(payload, queue.Type)

	if err != nil {
		queue.Attempts++
		if queue.Attempts >= p.config.RetryCount {
			log.Printf("💀 [Queue %d] Failed after %d attempts: %v", queueID, queue.Attempts, err)
			p.markFailed(queueID, err.Error())
		} else {
			// Reset status to 'pending' so job can be retried on restart or by retry logic
			p.db.Exec(`UPDATE webhook_queue SET status = 'pending', attempts = ?, error = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
				queue.Attempts, err.Error(), queueID)
			log.Printf("⚠️ [Queue %d] Retry %d/%d: %v (status set to pending)", queueID, queue.Attempts, p.config.RetryCount, err)
		}
		return
	}

	p.db.Exec(`UPDATE webhook_queue SET status = 'completed', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, queueID)

	duration := time.Since(startTime).Milliseconds()
	log.Printf("✅ [Queue %d] Relay Success: %s (%dms)", queueID, queue.Type, duration)
}

func (p *Pool) forwardToSharedHosting(payload interface{}, webhookType string) error {
	// Pilih URL berdasarkan type
	var url string
	switch webhookType {
	case "SALES_INVOICE":
		url = p.config.SharedHostingURLInvoice
	case "SALES_RECEIPT":
		url = p.config.SharedHostingURLReceipt
	default:
		url = p.config.SharedHostingURL
	}

	// Fallback ke default URL jika URL khusus kosong
	if url == "" {
		url = p.config.SharedHostingURL
	}

	if url == "" {
		return fmt.Errorf("shared hosting URL empty for type %s", webhookType)
	}

	data, err := json.Marshal(map[string]interface{}{
		"payload":   payload,
		"timestamp": time.Now().Format(time.RFC3339),
	})
	if err != nil {
		return fmt.Errorf("marshal payload error: %v", err)
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		return fmt.Errorf("create request error: %v", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Webhook-Secret", p.config.SharedHostingSecret)
	req.Header.Set("X-Webhook-Source", "VPS-GOLANG-RELAY")
	req.Header.Set("X-Webhook-Type", webhookType)

	client := &http.Client{Timeout: 30 * time.Second}
	startTime := time.Now()
	resp, err := client.Do(req)
	duration := time.Since(startTime).Milliseconds()

	if err != nil {
		log.Printf("❌ [Forward] HTTP request error: %v (took %dms)", err, duration)
		return fmt.Errorf("HTTP request error: %v", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		log.Printf("❌ [Forward] Shared hosting returned status %d: %s (took %dms)", resp.StatusCode, string(body), duration)
		return fmt.Errorf("shared hosting status %d: %s", resp.StatusCode, string(body))
	}

	log.Printf("✅ [Forward] Shared hosting responded OK (status %d, took %dms)", resp.StatusCode, duration)
	return nil
}

func (p *Pool) checkWebhookActivePeriod() {
	p.renewMu.Lock()
	defer p.renewMu.Unlock()

	var expiryStr string
	err := p.db.QueryRow(`SELECT timestamp FROM accurate_webhook_active_period LIMIT 1`).Scan(&expiryStr)
	if err == sql.ErrNoRows {
		newExpiry := time.Now().AddDate(0, 0, 5).Format("2006-01-02 15:04:05")
		_, insErr := p.db.Exec(`INSERT INTO accurate_webhook_active_period (timestamp, updated_at) VALUES (?, CURRENT_TIMESTAMP)`, newExpiry)
		if insErr != nil {
			log.Printf("[Renewal] Failed to seed active-period cache: %v", insErr)
			return
		}
		log.Printf("[Renewal] First webhook detected, cache set until %s", newExpiry)
		return
	}
	if err != nil {
		log.Printf("[Renewal] Failed reading active-period cache: %v", err)
		return
	}

	expiry, err := time.Parse("2006-01-02 15:04:05", expiryStr)
	if err != nil {
		expiry, _ = time.Parse(time.RFC3339, expiryStr)
	}
	if expiry.IsZero() {
		log.Printf("[Renewal] Invalid cache timestamp: %q", expiryStr)
		return
	}

	if time.Now().Before(expiry) {
		return
	}

	log.Println("[Renewal] Cache expired, renewing Accurate subscription...")

	if p.config.AccurateRenewURL == "" {
		log.Println("[Renewal] ACCURATE_RENEW_URL empty, skip renewal")
		return
	}

	headers := p.prepareApiHeaders()
	_, err = p.makeAPIRequest("POST", p.config.AccurateRenewURL, headers, nil)
	if err != nil {
		log.Printf("[Renewal] Failed: %v", err)
		return
	}

	newExpiry := time.Now().AddDate(0, 0, 5).Format("2006-01-02 15:04:05")
	res, updErr := p.db.Exec(`UPDATE accurate_webhook_active_period SET timestamp = ?, updated_at = CURRENT_TIMESTAMP`, newExpiry)
	if updErr != nil {
		log.Printf("[Renewal] Renew succeeded but cache update failed: %v", updErr)
		return
	}

	affected, _ := res.RowsAffected()
	if affected == 0 {
		_, _ = p.db.Exec(`INSERT INTO accurate_webhook_active_period (timestamp, updated_at) VALUES (?, CURRENT_TIMESTAMP)`, newExpiry)
	}

	log.Printf("[Renewal] Webhook renewed, cache set until %s", newExpiry)
}

func (p *Pool) prepareApiHeaders() map[string]string {
	timestamp := time.Now().Format("02/01/2006 15:04:05")
	h := hmac.New(sha256.New, []byte(p.config.AccurateSignatureSecret))
	h.Write([]byte(timestamp))
	signature := fmt.Sprintf("%x", h.Sum(nil))

	return map[string]string{
		"Authorization":   "Bearer " + p.config.AccurateAuthToken,
		"X-Api-Timestamp": timestamp,
		"X-Api-Signature": signature,
		"Content-Type":    "application/json",
	}
}

func (p *Pool) makeAPIRequest(method, url string, headers map[string]string, body []byte) ([]byte, error) {
	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

func (p *Pool) markFailed(queueID int64, errorMsg string) {
	p.db.Exec(`UPDATE webhook_queue SET status = 'failed', error = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
		errorMsg, queueID)
	log.Printf("💀 [Queue %d] Failed: %s", queueID, errorMsg)
}

// isDraftPayload checks if payload is a draft (DFT) that should be skipped
func (p *Pool) isDraftPayload(payload interface{}, webhookType string) bool {
	payloadMap, ok := payload.(map[string]interface{})
	if !ok {
		return false
	}

	data, ok := payloadMap["data"].([]interface{})
	if !ok || len(data) == 0 {
		return false
	}

	firstData, ok := data[0].(map[string]interface{})
	if !ok {
		return false
	}

	switch webhookType {
	case "SALES_RECEIPT":
		receiptNo, ok := firstData["salesReceiptNo"].(string)
		if ok && len(receiptNo) >= 3 && receiptNo[:3] == "DFT" {
			return true
		}
	case "SALES_INVOICE":
		invoiceNo, ok := firstData["salesInvoiceNo"].(string)
		if ok && len(invoiceNo) >= 3 && invoiceNo[:3] == "DFT" {
			return true
		}
	}

	return false
}

