package payments

import (
	"crussell/clock"
	"crussell/db"
	"crussell/internal/square"
	"crussell/internal/validators"
	"crussell/mw"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"log/slog"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5"
)

type TillSaleRequest struct {
	ItemType        string  `json:"item_type" validate:"required"`
	Action          string  `json:"action" validate:"required"`
	Amount          float64 `json:"amount" validate:"required,gt=0"`
	GiftCardID      *string `json:"gift_card_id,omitempty"`
	PaymentMethod   string  `json:"payment_method" validate:"required"`
	UserSavedCardID *string `json:"user_saved_card_id,omitempty"`
	UserID          *string `json:"user_id,omitempty"`
	IdempotencyKey  string  `json:"idempotency_key,omitempty"`
	CardNumber      string  `json:"card_number,omitempty"`
	CardExpMonth    int     `json:"card_exp_month,omitempty"`
	CardExpYear     int     `json:"card_exp_year,omitempty"`
	CardCVC         string  `json:"card_cvc,omitempty"`
	RedeemToUserID  *string `json:"redeem_to_user_id,omitempty"`
}

type TillSaleResponse struct {
	ID            string  `json:"id"`
	ItemType      string  `json:"item_type"`
	ItemID        *string `json:"item_id,omitempty"`
	TotalAmount   float64 `json:"total_amount"`
	PaymentMethod string  `json:"payment_method"`
	Status        string  `json:"status"`
	CheckoutID    *string `json:"checkout_id,omitempty"`
}

func CreateTillSale(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	adminID, _ := ctx.Value(mw.UserIDKey).(string)

	var req TillSaleRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	if err := validators.Validate.Struct(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// M8
	// L5

	if req.ItemType != "gift_card" {
		http.Error(w, "Unsupported item type", http.StatusBadRequest)
		return
	}
	if req.Action != "create" && req.Action != "topup" {
		http.Error(w, "Action must be 'create' or 'topup'", http.StatusBadRequest)
		return
	}
	if req.Amount <= 0 {
		http.Error(w, "Amount must be greater than zero", http.StatusBadRequest)
		return
	}
	if req.PaymentMethod != "cash" && req.PaymentMethod != "card_machine" && req.PaymentMethod != "saved_card" && req.PaymentMethod != "online_square" && req.PaymentMethod != "on_the_house" {
		http.Error(w, "Payment method must be 'cash', 'card_machine', 'saved_card', 'online_square', or 'on_the_house'", http.StatusBadRequest)
		return
	}
	if req.PaymentMethod == "saved_card" && (req.UserSavedCardID == nil || *req.UserSavedCardID == "") {
		http.Error(w, "user_saved_card_id is required when payment method is saved_card", http.StatusBadRequest)
		return
	}
	if req.PaymentMethod == "online_square" && req.CardNumber == "" {
		http.Error(w, "card_number is required when payment method is online_square", http.StatusBadRequest)
		return
	}
	if req.Action == "topup" && (req.GiftCardID == nil || *req.GiftCardID == "") {
		http.Error(w, "gift_card_id is required for topup", http.StatusBadRequest)
		return
	}

	// Idempotency check: if key provided, return existing sale if found
	if req.IdempotencyKey != "" {
		var existingID string
		err := db.Conn.QueryRow(ctx, `SELECT id FROM till_sales WHERE idempotency_key = $1`, req.IdempotencyKey).Scan(&existingID)
		if err == nil {
			// Existing sale found — return it (idempotent)
		_ = json.NewEncoder(w).Encode(TillSaleResponse{
			ID:            existingID,
			ItemType:      req.ItemType,
			TotalAmount:   req.Amount,
			PaymentMethod: req.PaymentMethod,
			Status:        "completed",
			})
			return
		}
	}

	service := NewPaymentService()

	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		log.Printf("Failed to begin transaction: %v", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	var giftCardID string
	if req.Action == "create" {
		var purchaseVoucherType string
		err = tx.QueryRow(ctx, `SELECT COALESCE(voucher_type, 'SPV') FROM business_settings LIMIT 1`).Scan(&purchaseVoucherType)
		if err != nil {
			log.Printf("Failed to query voucher type: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		if purchaseVoucherType == "" {
			purchaseVoucherType = "SPV"
		}
		err = tx.QueryRow(ctx, `
			INSERT INTO gift_cards (total_funds_added, amount_remaining, created_by, is_inventory, voucher_type_at_purchase)
			VALUES ($1, $1, $2, FALSE, $3)
			RETURNING id
		`, req.Amount, adminID, purchaseVoucherType).Scan(&giftCardID)
		if err != nil {
			log.Printf("Failed to create gift card: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		_, err = tx.Exec(ctx, `
			INSERT INTO gift_card_transactions (gift_card_id, transaction_type, amount, reference_type, reference_id, user_id, notes)
			VALUES ($1, 'purchase', $2, 'till_sale', NULL, $3, NULL)
		`, giftCardID, req.Amount, req.UserID)
		if err != nil {
			log.Printf("Failed to create gift_card_transaction: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
	} else {
		cardID := validators.NormalizeGiftCardCode(*req.GiftCardID)
		var redeemedBy sql.NullString
		err = tx.QueryRow(ctx, "SELECT redeemed_by FROM gift_cards WHERE id = $1", cardID).Scan(&redeemedBy)
		if err != nil {
			if errors.Is(err, pgx.ErrNoRows) {
				http.Error(w, "Gift card not found", http.StatusNotFound)
				return
			}
			log.Printf("Failed to check gift card: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		if redeemedBy.Valid {
			http.Error(w, "Cannot top up a card that has been redeemed to an account", http.StatusBadRequest)
			return
		}

		var isInventory bool
		var previousTotal float64
		err = tx.QueryRow(ctx, `SELECT is_inventory, total_funds_added FROM gift_cards WHERE id = $1`, cardID).Scan(&isInventory, &previousTotal)
		if err != nil {
			log.Printf("Failed to check gift card state: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		_, err = tx.Exec(ctx, `
			UPDATE gift_cards
			SET total_funds_added = total_funds_added + $1,
			    amount_remaining = amount_remaining + $1,
			    last_used_at = NOW()
			WHERE id = $2
		`, req.Amount, cardID)
		if err != nil {
			log.Printf("Failed to top up gift card: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		transactionType := "topup"
		var notes *string
		if isInventory && previousTotal == 0 {
			transactionType = "purchase"
			n := "first top-up on inventory card"
			notes = &n
		}
		_, err = tx.Exec(ctx, `
			INSERT INTO gift_card_transactions (gift_card_id, transaction_type, amount, reference_type, reference_id, user_id, notes)
			VALUES ($1, $2, $3, 'till_sale', NULL, $4, $5)
		`, cardID, transactionType, req.Amount, req.UserID, notes)
		if err != nil {
			log.Printf("Failed to create gift_card_transaction: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		giftCardID = cardID
	}

	// If the gift card should be immediately redeemed to a user's account balance
	// (e.g. admin selected "add to account" rather than "generate gift code")
	if req.RedeemToUserID != nil && *req.RedeemToUserID != "" {
		_, err = tx.Exec(ctx, `
			UPDATE gift_cards
			SET amount_remaining = 0,
			    redeemed_at = NOW(),
			    redeemed_by = $1,
			    last_used_at = NOW()
			WHERE id = $2
		`, *req.RedeemToUserID, giftCardID)
		if err != nil {
			log.Printf("Failed to redeem gift card to user account: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		_, err = tx.Exec(ctx, `
			INSERT INTO user_giftcard_balances (user_id, balance, updated_at)
			VALUES ($1, $2, NOW())
			ON CONFLICT (user_id) DO UPDATE SET
				balance = user_giftcard_balances.balance + EXCLUDED.balance,
				updated_at = NOW()
		`, *req.RedeemToUserID, req.Amount)
		if err != nil {
			log.Printf("Failed to update user gift card balance: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
	}

	penceAmount := int64(req.Amount * 100)

	var squarePaymentID *string
	var squareCheckoutID *string
	var saleStatus string
	var dbPaymentMethod string

	// Post-commit Square payment tracking: saved_card and online_square
	// call Square AFTER the DB transaction commits, so a tx failure never
	// leaves a Square charge with no DB record.
	var needsSquarePayment bool
	var savedCardSqCardID string

	switch req.PaymentMethod {
	case "cash":
		saleStatus = "completed"
		dbPaymentMethod = "cash"
		if req.IdempotencyKey == "" {
			req.IdempotencyKey = "till-cash-" + giftCardID + "-" + clock.Now().Format("20060102150405.000000")
		}
	case "saved_card":
		dbPaymentMethod = "online_square"
		if req.UserID != nil && *req.UserID != "" {
			_, err = service.GetCardByIDQuerier(ctx, tx, *req.UserSavedCardID, *req.UserID)
			if err != nil {
				if errors.Is(err, pgx.ErrNoRows) {
					http.Error(w, "Saved card not found", http.StatusNotFound)
					return
				}
				log.Printf("Failed to verify saved card: %v", err)
				http.Error(w, "internal server error", http.StatusInternalServerError)
				return
			}
		}

		err = tx.QueryRow(ctx, `
			SELECT square_card_id
			FROM user_saved_cards
			WHERE id = $1 AND deleted_at IS NULL
		`, *req.UserSavedCardID).Scan(&savedCardSqCardID)
		if err != nil {
			log.Printf("Failed to get saved card details: %v", err)
			http.Error(w, "Card not found", http.StatusNotFound)
			return
		}

		if req.IdempotencyKey == "" {
			req.IdempotencyKey = "till-sale-" + giftCardID + "-" + clock.Now().Format("20060102150405.000000")
		}

		saleStatus = "pending"
		needsSquarePayment = true
	case "card_machine":
		dbPaymentMethod = "in_person_card"
		if req.IdempotencyKey == "" {
			req.IdempotencyKey = "till-terminal-" + giftCardID + "-" + clock.Now().Format("20060102150405.000000")
		}

		checkoutReq := square.CreateCheckoutReq{
			Amount:         penceAmount,
			Currency:       "GBP",
			IdempotencyKey: req.IdempotencyKey,
			ReferenceID:    giftCardID,
			TipEnabled:     false,
		}

		checkout, err := SquareClient.CreateCheckout(ctx, checkoutReq)
		if err != nil {
			log.Printf("Failed to create Square checkout: %v", err)
			http.Error(w, "Failed to create card machine payment", http.StatusInternalServerError)
			return
		}

		squareCheckoutID = &checkout.ID
		saleStatus = "pending"
	case "online_square":
		dbPaymentMethod = "online_square"
		if req.IdempotencyKey == "" {
			req.IdempotencyKey = "till-online-" + giftCardID + "-" + clock.Now().Format("20060102150405.000000")
		}

		saleStatus = "pending"
		needsSquarePayment = true
	case "on_the_house":
		saleStatus = "completed"
		dbPaymentMethod = "on_the_house"
		if req.IdempotencyKey == "" {
			req.IdempotencyKey = "till-on-the-house-" + giftCardID + "-" + clock.Now().Format("20060102150405.000000")
		}
	}

	desc := fmt.Sprintf("Gift Card %s (£%.2f)", req.Action, req.Amount)

	var tillSaleID string
	err = tx.QueryRow(ctx, `
		INSERT INTO till_sales (
			item_type, item_id, description, quantity, unit_price, total_amount,
			payment_method, status, user_id, user_saved_card_id,
			square_payment_id, square_checkout_id, idempotency_key, notes, created_by, created_at, updated_at
		) VALUES ($1, $2, $3, 1, $4, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, NOW(), NOW())
		RETURNING id
	`,
		req.ItemType,
		giftCardID,
		desc,
		req.Amount,
		dbPaymentMethod,
		saleStatus,
		req.UserID,
		req.UserSavedCardID,
		squarePaymentID,
		squareCheckoutID,
		req.IdempotencyKey,
		"Admin till sale: "+req.Action+" gift card",
		adminID,
	).Scan(&tillSaleID)
	if err != nil {
		log.Printf("Failed to insert till sale: %v", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	_, err = tx.Exec(ctx, `
		UPDATE gift_card_transactions SET reference_id = $1
		WHERE gift_card_id = $2 AND reference_id IS NULL AND created_at > NOW() - INTERVAL '5 seconds'
	`, tillSaleID, giftCardID)
	if err != nil {
		log.Printf("Failed to update gift_card_transactions reference: %v", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	if req.PaymentMethod != "on_the_house" && (saleStatus == "completed" || needsSquarePayment) {
		vatCfg, vatErr := GetVATConfig(ctx, tx)
		if vatErr == nil && vatCfg.IsVATRegistered && vatCfg.VoucherType == "SPV" {
			if _, vatExecErr := tx.Exec(ctx, "SELECT apply_vat_to_till_sale($1, $2)", tillSaleID, vatCfg.DefaultVATRate); vatExecErr != nil {
				log.Printf("Failed to apply VAT to till sale %s: %v", tillSaleID, vatExecErr)
			}
		}
	}

	if err := tx.Commit(ctx); err != nil {
		log.Printf("Failed to commit till sale transaction: %v", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	// Step 2: DB transaction committed — safe to call Square now.
	// If Square fails, the till_sale record stays 'pending' for manual retry.
	if needsSquarePayment {
		var paymentResult *square.PaymentResult
		var squareErr error

		if req.PaymentMethod == "saved_card" {
			paymentReq := square.CreatePaymentReq{
				Amount:         penceAmount,
				Currency:       "GBP",
				SourceID:       savedCardSqCardID,
				IdempotencyKey: req.IdempotencyKey,
				Note:           "Gift Card " + req.Action,
			}
			paymentResult, squareErr = SquareClient.CreatePayment(ctx, paymentReq)
		} else if req.PaymentMethod == "online_square" {
			cardOnFile, cardErr := SquareClient.CreateCardOnFileRaw(ctx, "till-"+giftCardID, req.CardNumber, req.CardExpMonth, req.CardExpYear, req.CardCVC)
			if cardErr != nil {
				log.Printf("Failed to tokenize ephemeral card: %v", cardErr)
				http.Error(w, "Card tokenization failed", http.StatusInternalServerError)
				return
			}

			paymentReq := square.CreatePaymentReq{
				Amount:         penceAmount,
				Currency:       "GBP",
				SourceID:       cardOnFile.CardID,
				IdempotencyKey: req.IdempotencyKey,
				Note:           "Gift Card " + req.Action,
			}
			paymentResult, squareErr = SquareClient.CreatePayment(ctx, paymentReq)
		}

		if squareErr != nil {
			log.Printf("Failed to process payment: %v", squareErr)
			http.Error(w, "Payment failed", http.StatusPaymentRequired)
			return
		}

		// Square succeeded — update the till_sale record.
		_, upErr := db.Conn.Exec(ctx,
			`UPDATE till_sales SET status = 'completed', square_payment_id = $1 WHERE id = $2`,
			paymentResult.SquarePayID, tillSaleID,
		)
		if upErr != nil {
			log.Printf("CRITICAL: Square payment succeeded (ID=%s) but till_sale %s update failed: %v — manual reconciliation required", paymentResult.SquarePayID, tillSaleID, upErr)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		saleStatus = "completed"
	}

	w.WriteHeader(http.StatusCreated)
	_ = json.NewEncoder(w).Encode(TillSaleResponse{
		ID:            tillSaleID,
		ItemType:      req.ItemType,
		ItemID:        &giftCardID,
		TotalAmount:   req.Amount,
		PaymentMethod: req.PaymentMethod,
		Status:        saleStatus,
		CheckoutID:    squareCheckoutID,
	})
}

func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) {
	checkoutID := chi.URLParam(r, "checkout_id")
	if checkoutID == "" {
		http.Error(w, "Checkout ID is required", http.StatusBadRequest)
		return
	}

	var tillSaleID string
	var currentStatus string
	err := db.Conn.QueryRow(r.Context(), `
		SELECT id, status FROM till_sales
		WHERE square_checkout_id = $1
	`, checkoutID).Scan(&tillSaleID, &currentStatus)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			http.Error(w, "Till sale not found", http.StatusNotFound)
			return
		}
		log.Printf("Failed to find till sale: %v", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	if currentStatus == "completed" {
		_ = json.NewEncoder(w).Encode(PaymentStatusResponse{
			Status: "COMPLETED",
		})
		return
	}

	paymentResult, err := SquareClient.GetCheckout(r.Context(), checkoutID)
	if err != nil {
		if err.Error() == "checkout pending" {
			_ = json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"})
			return
		}
		log.Printf("Failed to get checkout status: %v", err)
		http.Error(w, "Failed to get checkout status", http.StatusInternalServerError)
		return
	}

	if paymentResult.Status == "COMPLETED" {
		tx, err := db.Conn.Begin(r.Context())
		if err != nil {
			log.Printf("Failed to begin transaction: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		defer func() {
    if err := tx.Rollback(r.Context()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

		_, err = tx.Exec(r.Context(), `
			UPDATE till_sales
			SET status = 'completed',
			    square_payment_id = $1,
			    updated_at = NOW()
			WHERE id = $2
		`, paymentResult.SquarePayID, tillSaleID)
		if err != nil {
			log.Printf("Failed to update till sale: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		ApplyVATToTillSale(r.Context(), tx, tillSaleID)

		if err := tx.Commit(r.Context()); err != nil {
			log.Printf("Failed to commit transaction: %v", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		_ = json.NewEncoder(w).Encode(PaymentStatusResponse{
			Status:     "COMPLETED",
			PaymentID:  tillSaleID,
			Amount:     paymentResult.Amount,
			CardBrand:  paymentResult.CardBrand,
			CardLast4:  paymentResult.CardLast4,
			ReceiptURL: paymentResult.ReceiptURL,
		})
		return
	}

	_ = json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"})
}
