package repository

import (
	dbgen "Yimaru-Backend/gen/db"
	"Yimaru-Backend/internal/domain"
	"context"

	"github.com/jackc/pgx/v5/pgtype"
)

func (s *Store) CreatePayment(ctx context.Context, input domain.CreatePaymentInput) (*domain.Payment, error) {
	payment, err := s.queries.CreatePayment(ctx, dbgen.CreatePaymentParams{
		UserID:         input.UserID,
		PlanID:         int64PtrToPgInt8(input.PlanID),
		SubscriptionID: pgtype.Int8{Valid: false},
		SessionID:      pgtype.Text{Valid: false},
		TransactionID:  pgtype.Text{Valid: false},
		Nonce:          input.Nonce,
		Amount:         toPgNumeric(input.Amount),
		Currency:       input.Currency,
		PaymentMethod:  toPgText(input.PaymentMethod),
		Column10:       string(domain.PaymentStatusPending), // status with COALESCE
		PaymentUrl:     pgtype.Text{Valid: false},
		ExpiresAt:      toPgTimestamptzPtr(input.ExpiresAt),
	})
	if err != nil {
		return nil, err
	}
	return paymentToDomain(payment), nil
}

func (s *Store) GetPaymentByID(ctx context.Context, id int64) (*domain.Payment, error) {
	payment, err := s.queries.GetPaymentByID(ctx, id)
	if err != nil {
		return nil, err
	}
	return paymentToDomain(payment), nil
}

func (s *Store) GetPaymentBySessionID(ctx context.Context, sessionID string) (*domain.Payment, error) {
	payment, err := s.queries.GetPaymentBySessionID(ctx, toPgText(&sessionID))
	if err != nil {
		return nil, err
	}
	return paymentToDomain(payment), nil
}

func (s *Store) GetPaymentByNonce(ctx context.Context, nonce string) (*domain.Payment, error) {
	payment, err := s.queries.GetPaymentByNonce(ctx, nonce)
	if err != nil {
		return nil, err
	}
	return paymentToDomain(payment), nil
}

func (s *Store) GetPaymentByTransactionID(ctx context.Context, transactionID string) (*domain.Payment, error) {
	payment, err := s.queries.GetPaymentByTransactionID(ctx, toPgText(&transactionID))
	if err != nil {
		return nil, err
	}
	return paymentToDomain(payment), nil
}

func (s *Store) GetPaymentsByUserID(ctx context.Context, userID int64, limit, offset int32) ([]domain.Payment, error) {
	payments, err := s.queries.GetPaymentsByUserID(ctx, dbgen.GetPaymentsByUserIDParams{
		UserID: userID,
		Limit:  pgtype.Int4{Int32: limit, Valid: true},
		Offset: pgtype.Int4{Int32: offset, Valid: true},
	})
	if err != nil {
		return nil, err
	}

	result := make([]domain.Payment, len(payments))
	for i, p := range payments {
		result[i] = domain.Payment{
			ID:             p.ID,
			UserID:         p.UserID,
			PlanID:         int8PtrToInt64Ptr(p.PlanID),
			SubscriptionID: int8PtrToInt64Ptr(p.SubscriptionID),
			SessionID:      fromPgTextPtr(p.SessionID),
			TransactionID:  fromPgTextPtr(p.TransactionID),
			Nonce:          p.Nonce,
			Amount:         fromPgNumeric(p.Amount),
			Currency:       p.Currency,
			PaymentMethod:  fromPgTextPtr(p.PaymentMethod),
			Status:         p.Status,
			PaymentURL:     fromPgTextPtr(p.PaymentUrl),
			PaidAt:         timePtr(p.PaidAt),
			ExpiresAt:      timePtr(p.ExpiresAt),
			CreatedAt:      p.CreatedAt.Time,
			UpdatedAt:      timePtr(p.UpdatedAt),
			PlanName:       fromPgTextPtr(p.PlanName),
		}
	}
	return result, nil
}

func (s *Store) GetPendingPaymentsByUserID(ctx context.Context, userID int64) ([]domain.Payment, error) {
	payments, err := s.queries.GetPendingPaymentsByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}

	result := make([]domain.Payment, len(payments))
	for i, p := range payments {
		result[i] = *paymentToDomain(p)
	}
	return result, nil
}

func (s *Store) UpdatePaymentStatus(ctx context.Context, id int64, status string) error {
	return s.queries.UpdatePaymentStatus(ctx, dbgen.UpdatePaymentStatusParams{
		Status: status,
		ID:     id,
	})
}

func (s *Store) UpdatePaymentStatusBySessionID(ctx context.Context, sessionID, status, transactionID, paymentMethod string) error {
	return s.queries.UpdatePaymentStatusBySessionID(ctx, dbgen.UpdatePaymentStatusBySessionIDParams{
		Status:        status,
		TransactionID: toPgText(&transactionID),
		PaymentMethod: toPgText(&paymentMethod),
		SessionID:     toPgText(&sessionID),
	})
}

func (s *Store) UpdatePaymentStatusByNonce(ctx context.Context, nonce, status, transactionID, paymentMethod string) error {
	return s.queries.UpdatePaymentStatusByNonce(ctx, dbgen.UpdatePaymentStatusByNonceParams{
		Status:        status,
		TransactionID: toPgText(&transactionID),
		PaymentMethod: toPgText(&paymentMethod),
		Nonce:         nonce,
	})
}

func (s *Store) UpdatePaymentSessionID(ctx context.Context, id int64, sessionID, paymentURL string) error {
	return s.queries.UpdatePaymentSessionID(ctx, dbgen.UpdatePaymentSessionIDParams{
		SessionID:  toPgText(&sessionID),
		PaymentUrl: toPgText(&paymentURL),
		ID:         id,
	})
}

func (s *Store) LinkPaymentToSubscription(ctx context.Context, paymentID, subscriptionID int64) error {
	return s.queries.LinkPaymentToSubscription(ctx, dbgen.LinkPaymentToSubscriptionParams{
		SubscriptionID: pgtype.Int8{Int64: subscriptionID, Valid: true},
		ID:             paymentID,
	})
}

func (s *Store) GetExpiredPendingPayments(ctx context.Context) ([]domain.Payment, error) {
	payments, err := s.queries.GetExpiredPendingPayments(ctx)
	if err != nil {
		return nil, err
	}

	result := make([]domain.Payment, len(payments))
	for i, p := range payments {
		result[i] = *paymentToDomain(p)
	}
	return result, nil
}

func (s *Store) ExpirePayment(ctx context.Context, id int64) error {
	return s.queries.ExpirePayment(ctx, id)
}

// Helper functions

func paymentToDomain(p dbgen.Payment) *domain.Payment {
	return &domain.Payment{
		ID:             p.ID,
		UserID:         p.UserID,
		PlanID:         int8PtrToInt64Ptr(p.PlanID),
		SubscriptionID: int8PtrToInt64Ptr(p.SubscriptionID),
		SessionID:      fromPgTextPtr(p.SessionID),
		TransactionID:  fromPgTextPtr(p.TransactionID),
		Nonce:          p.Nonce,
		Amount:         fromPgNumeric(p.Amount),
		Currency:       p.Currency,
		PaymentMethod:  fromPgTextPtr(p.PaymentMethod),
		Status:         p.Status,
		PaymentURL:     fromPgTextPtr(p.PaymentUrl),
		PaidAt:         timePtr(p.PaidAt),
		ExpiresAt:      timePtr(p.ExpiresAt),
		CreatedAt:      p.CreatedAt.Time,
		UpdatedAt:      timePtr(p.UpdatedAt),
	}
}

func int64PtrToPgInt8(val *int64) pgtype.Int8 {
	if val == nil {
		return pgtype.Int8{Valid: false}
	}
	return pgtype.Int8{Int64: *val, Valid: true}
}

func int8PtrToInt64Ptr(val pgtype.Int8) *int64 {
	if !val.Valid {
		return nil
	}
	return &val.Int64
}

func fromPgTextPtr(t pgtype.Text) *string {
	if !t.Valid {
		return nil
	}
	return &t.String
}

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