package services

import (
	"context"
	"fmt"
	"math"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"ticketing-backend/internal/models"
)

type PromoService struct {
	pool *pgxpool.Pool
}

func NewPromoService(pool *pgxpool.Pool) *PromoService {
	return &PromoService{pool: pool}
}

func (s *PromoService) ValidatePromoCode(code, userID, userEmail, eventID string, originalAmount float64) (*models.PromoValidation, error) {
	ctx := context.Background()

	// Find promo by code
	var promo models.Promo
	err := s.pool.QueryRow(ctx,
		`SELECT * FROM promos
		 WHERE UPPER(code) = UPPER($1)
		 AND is_active = TRUE
		 AND valid_from <= NOW()
		 AND valid_until >= NOW()`, code,
	).Scan(
		&promo.ID, &promo.Code, &promo.Name, &promo.Description,
		&promo.PromoType, &promo.Value, &promo.MinPurchase, &promo.MaxDiscount,
		&promo.UsageLimit, &promo.UsageCount, &promo.PerUserLimit,
		&promo.EventID, &promo.ValidFrom, &promo.ValidUntil,
		&promo.IsActive, &promo.CreatedBy, &promo.CreatedAt, &promo.UpdatedAt,
	)

	if err != nil {
		// Check if promo exists but is inactive/expired
		var exists bool
		s.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM promos WHERE UPPER(code) = UPPER($1))`, code).Scan(&exists)
		if exists {
			var existingPromo models.Promo
			if err := s.pool.QueryRow(ctx,
				`SELECT code, is_active, valid_from, valid_until FROM promos WHERE UPPER(code) = UPPER($1)`, code,
			).Scan(&existingPromo.Code, &existingPromo.IsActive, &existingPromo.ValidFrom, &existingPromo.ValidUntil); err == nil {
				now := time.Now()
				if !existingPromo.IsActive {
					return &models.PromoValidation{Valid: false, Message: strPtr("Kode promo sudah tidak aktif")}, nil
				}
				if now.Before(existingPromo.ValidFrom) {
					return &models.PromoValidation{Valid: false, Message: strPtr(fmt.Sprintf("Kode promo belum berlaku (mulai %s)", existingPromo.ValidFrom.Format("2 Jan 2006")))}, nil
				}
				if now.After(existingPromo.ValidUntil) {
					return &models.PromoValidation{Valid: false, Message: strPtr(fmt.Sprintf("Kode promo sudah berakhir (berakhir %s)", existingPromo.ValidUntil.Format("2 Jan 2006")))}, nil
				}
			}
		}
		return &models.PromoValidation{Valid: false, Message: strPtr("Kode promo tidak ditemukan atau sudah tidak berlaku")}, nil
	}

	// Check event restriction
	if promo.EventID != nil && *promo.EventID != eventID {
		return &models.PromoValidation{Valid: false, Message: strPtr("Kode promo tidak berlaku untuk event ini")}, nil
	}

	// Check minimum purchase
	if originalAmount < promo.MinPurchase {
		return &models.PromoValidation{
			Valid: false, Message: strPtr(fmt.Sprintf("Minimum pembelian Rp %.0f", promo.MinPurchase)),
		}, nil
	}

	// Check usage limit
	if promo.UsageLimit != nil && promo.UsageCount >= *promo.UsageLimit {
		return &models.PromoValidation{Valid: false, Message: strPtr("Kode promo sudah mencapai batas penggunaan")}, nil
	}

	// Check per-user limit
	var userUsage int
	s.pool.QueryRow(ctx,
		`SELECT COUNT(*) FROM promo_usages WHERE promo_id = $1 AND user_id = $2`,
		promo.ID, userID,
	).Scan(&userUsage)
	if userUsage >= promo.PerUserLimit {
		return &models.PromoValidation{Valid: false, Message: strPtr("Anda sudah menggunakan kode promo ini")}, nil
	}

	// Calculate discount
	discountAmount := calculateDiscount(string(promo.PromoType), promo.Value, originalAmount, promo.MaxDiscount)
	finalAmount := math.Max(0, originalAmount-discountAmount)

	return &models.PromoValidation{
		Valid:          true,
		Promo:          &promo,
		DiscountAmount: discountAmount,
		FinalAmount:    finalAmount,
	}, nil
}

func (s *PromoService) CheckDomainPromo(userEmail, eventID string, originalAmount float64) (*models.PromoValidation, error) {
	ctx := context.Background()

	// Extract domain from email
	parts := splitEmail(userEmail)
	if len(parts) < 2 {
		return &models.PromoValidation{Valid: false, DiscountAmount: 0, FinalAmount: originalAmount}, nil
	}
	domain := "@" + parts[1]

	var dp models.DomainPromo
	err := s.pool.QueryRow(ctx,
		`SELECT * FROM domain_promos
		 WHERE domain = $1 AND is_active = TRUE
		 AND (event_id IS NULL OR event_id = $2)
		 ORDER BY event_id DESC NULLS LAST
		 LIMIT 1`, domain, eventID,
	).Scan(&dp.ID, &dp.Domain, &dp.PromoType, &dp.Value, &dp.MaxDiscount,
		&dp.EventID, &dp.Description, &dp.IsActive, &dp.CreatedBy,
		&dp.CreatedAt, &dp.UpdatedAt)

	if err != nil {
		return &models.PromoValidation{Valid: false, DiscountAmount: 0, FinalAmount: originalAmount}, nil
	}

	discountAmount := calculateDiscount(string(dp.PromoType), dp.Value, originalAmount, dp.MaxDiscount)
	finalAmount := math.Max(0, originalAmount-discountAmount)

	return &models.PromoValidation{
		Valid:          true,
		DomainPromo:    &dp,
		DiscountAmount: discountAmount,
		FinalAmount:    finalAmount,
	}, nil
}

func (s *PromoService) RecordUsage(promoID, domainPromoID, userID, orderID *string, discountAmount float64) error {
	ctx := context.Background()
	_, err := s.pool.Exec(ctx,
		`INSERT INTO promo_usages (promo_id, domain_promo_id, user_id, order_id, discount_amount)
		 VALUES ($1, $2, $3, $4, $5)`,
		promoID, domainPromoID, userID, orderID, discountAmount,
	)
	if err != nil {
		return err
	}

	if promoID != nil && *promoID != "" {
		_, _ = s.pool.Exec(ctx,
			`UPDATE promos SET usage_count = usage_count + 1, updated_at = NOW() WHERE id = $1`,
			*promoID,
		)
	}
	return nil
}

func (s *PromoService) GetAllPromos(eventID *string) ([]models.Promo, error) {
	ctx := context.Background()
	query := `SELECT p.*, e.name as event_name FROM promos p LEFT JOIN events e ON p.event_id = e.id`
	var args []interface{}

	if eventID != nil {
		query += ` WHERE p.event_id = $1 OR p.event_id IS NULL`
		args = append(args, *eventID)
	}
	query += ` ORDER BY p.created_at DESC`

	rows, err := s.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var promos []models.Promo
	for rows.Next() {
		var p models.Promo
		var eventName *string
		err := rows.Scan(&p.ID, &p.Code, &p.Name, &p.Description, &p.PromoType,
			&p.Value, &p.MinPurchase, &p.MaxDiscount, &p.UsageLimit, &p.UsageCount,
			&p.PerUserLimit, &p.EventID, &p.ValidFrom, &p.ValidUntil, &p.IsActive,
			&p.CreatedBy, &p.CreatedAt, &p.UpdatedAt, &eventName)
		if err != nil {
			continue
		}
		p.EventName = eventName
		promos = append(promos, p)
	}
	return promos, nil
}

func (s *PromoService) CreatePromo(data map[string]interface{}, createdBy string) (*models.Promo, error) {
	ctx := context.Background()
	var promo models.Promo

	code, _ := data["code"].(string)
	name, _ := data["name"].(string)
	promoType, _ := data["promoType"].(string)
	value, _ := data["value"].(float64)

	validFrom := time.Now()
	if v, ok := data["validFrom"].(string); ok && v != "" {
		if t, err := time.Parse(time.RFC3339, v); err == nil {
			validFrom = t
		}
	}
	validUntil := time.Now().Add(365 * 24 * time.Hour)
	if v, ok := data["validUntil"].(string); ok && v != "" {
		if t, err := time.Parse(time.RFC3339, v); err == nil {
			validUntil = t
		}
	}

	description, _ := data["description"].(string)
	minPurchase, _ := data["minPurchase"].(float64)
	var maxDiscount *float64
	if v, ok := data["maxDiscount"].(float64); ok && v > 0 {
		maxDiscount = &v
	}
	var usageLimit *int
	if v, ok := data["usageLimit"].(float64); ok && v > 0 {
		ul := int(v)
		usageLimit = &ul
	}
	perUserLimit := 1
	if v, ok := data["perUserLimit"].(float64); ok && v > 0 {
		perUserLimit = int(v)
	}
	var eventID *string
	if v, ok := data["eventId"].(string); ok && v != "" {
		eventID = &v
	}

	var desc *string
	if description != "" {
		desc = &description
	}

	err := s.pool.QueryRow(ctx,
		`INSERT INTO promos (code, name, description, promo_type, value, min_purchase, max_discount,
			usage_limit, per_user_limit, event_id, valid_from, valid_until, is_active, created_by)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
		 RETURNING *`,
		code, name, desc, promoType, value, minPurchase, maxDiscount,
		usageLimit, perUserLimit, eventID, validFrom, validUntil, true, createdBy,
	).Scan(&promo.ID, &promo.Code, &promo.Name, &promo.Description, &promo.PromoType,
		&promo.Value, &promo.MinPurchase, &promo.MaxDiscount, &promo.UsageLimit,
		&promo.UsageCount, &promo.PerUserLimit, &promo.EventID, &promo.ValidFrom,
		&promo.ValidUntil, &promo.IsActive, &promo.CreatedBy, &promo.CreatedAt, &promo.UpdatedAt)

	if err != nil {
		return nil, fmt.Errorf("failed to create promo: %w", err)
	}
	return &promo, nil
}

func calculateDiscount(promoType string, value, originalAmount float64, maxDiscount *float64) float64 {
	var discount float64
	switch promoType {
	case "percentage":
		discount = originalAmount * value / 100
		if maxDiscount != nil && discount > *maxDiscount {
			discount = *maxDiscount
		}
	case "fixed":
		discount = value
	case "free":
		discount = originalAmount
	}
	return math.Min(discount, originalAmount)
}

func strPtr(s string) *string {
	return &s
}

func splitEmail(email string) []string {
	for i := 0; i < len(email); i++ {
		if email[i] == '@' {
			return []string{email[:i], email[i+1:]}
		}
	}
	return nil
}
