package repository

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

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

// Helper functions for numeric conversions
func toPgNumeric(val float64) pgtype.Numeric {
	d := decimal.NewFromFloat(val)
	var num pgtype.Numeric
	_ = num.Scan(d.String())
	return num
}

func fromPgNumeric(num pgtype.Numeric) float64 {
	if !num.Valid {
		return 0
	}
	f, _ := num.Float64Value()
	return f.Float64
}

func toPgTimestamptz(t time.Time) pgtype.Timestamptz {
	return pgtype.Timestamptz{Time: t, Valid: true}
}

func toPgTimestamptzPtr(t *time.Time) pgtype.Timestamptz {
	if t == nil {
		return pgtype.Timestamptz{Valid: false}
	}
	return pgtype.Timestamptz{Time: *t, Valid: true}
}

// =====================
// Subscription Plans
// =====================

func (s *Store) CreateSubscriptionPlan(ctx context.Context, input domain.CreateSubscriptionPlanInput) (*domain.SubscriptionPlan, error) {
	plan, err := s.queries.CreateSubscriptionPlan(ctx, dbgen.CreateSubscriptionPlanParams{
		Name:          input.Name,
		Description:   toPgText(input.Description),
		DurationValue: input.DurationValue,
		DurationUnit:  input.DurationUnit,
		Price:         toPgNumeric(input.Price),
		Currency:      input.Currency,
		Column7:       input.IsActive,
	})
	if err != nil {
		return nil, err
	}
	return subscriptionPlanToDomain(plan), nil
}

func (s *Store) GetSubscriptionPlanByID(ctx context.Context, id int64) (*domain.SubscriptionPlan, error) {
	plan, err := s.queries.GetSubscriptionPlanByID(ctx, id)
	if err != nil {
		return nil, err
	}
	return subscriptionPlanToDomain(plan), nil
}

func (s *Store) ListSubscriptionPlans(ctx context.Context, activeOnly bool) ([]domain.SubscriptionPlan, error) {
	var plans []dbgen.SubscriptionPlan
	var err error

	if activeOnly {
		plans, err = s.queries.ListActiveSubscriptionPlans(ctx)
	} else {
		plans, err = s.queries.ListSubscriptionPlans(ctx, false)
	}
	if err != nil {
		return nil, err
	}

	result := make([]domain.SubscriptionPlan, len(plans))
	for i, p := range plans {
		result[i] = *subscriptionPlanToDomain(p)
	}
	return result, nil
}

func (s *Store) UpdateSubscriptionPlan(ctx context.Context, id int64, input domain.UpdateSubscriptionPlanInput) error {
	return s.queries.UpdateSubscriptionPlan(ctx, dbgen.UpdateSubscriptionPlanParams{
		Name:          stringVal(input.Name),
		Description:   toPgText(input.Description),
		DurationValue: int32Val(input.DurationValue),
		DurationUnit:  stringVal(input.DurationUnit),
		Price:         numericPtrToNumeric(input.Price),
		Currency:      stringVal(input.Currency),
		IsActive:      boolPtrToBool(input.IsActive),
		ID:            id,
	})
}

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

// =====================
// User Subscriptions
// =====================

func (s *Store) CreateUserSubscription(ctx context.Context, input domain.CreateUserSubscriptionInput) (*domain.UserSubscription, error) {
	sub, err := s.queries.CreateUserSubscription(ctx, dbgen.CreateUserSubscriptionParams{
		UserID:           input.UserID,
		PlanID:           input.PlanID,
		Column3:          input.StartsAt,
		ExpiresAt:        toPgTimestamptz(input.ExpiresAt),
		Column5:          input.Status,
		PaymentReference: toPgText(input.PaymentReference),
		PaymentMethod:    toPgText(input.PaymentMethod),
		Column8:          input.AutoRenew,
	})
	if err != nil {
		return nil, err
	}
	return userSubscriptionToDomain(sub), nil
}

func (s *Store) GetUserSubscriptionByID(ctx context.Context, id int64) (*domain.UserSubscription, error) {
	sub, err := s.queries.GetUserSubscriptionByID(ctx, id)
	if err != nil {
		return nil, err
	}
	return userSubscriptionWithPlanToDomain(sub), nil
}

func (s *Store) GetActiveSubscriptionByUserID(ctx context.Context, userID int64) (*domain.UserSubscription, error) {
	sub, err := s.queries.GetActiveSubscriptionByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}
	return &domain.UserSubscription{
		ID:               sub.ID,
		UserID:           sub.UserID,
		PlanID:           sub.PlanID,
		StartsAt:         sub.StartsAt.Time,
		ExpiresAt:        sub.ExpiresAt.Time,
		Status:           sub.Status,
		PaymentReference: fromPgText(sub.PaymentReference),
		PaymentMethod:    fromPgText(sub.PaymentMethod),
		AutoRenew:        sub.AutoRenew,
		CancelledAt:      timePtr(sub.CancelledAt),
		CreatedAt:        sub.CreatedAt.Time,
		UpdatedAt:        timePtr(sub.UpdatedAt),
		PlanName:         &sub.PlanName,
		DurationValue:    &sub.DurationValue,
		DurationUnit:     &sub.DurationUnit,
		Price:            float64Ptr(fromPgNumeric(sub.Price)),
		Currency:         &sub.Currency,
	}, nil
}

func (s *Store) GetUserSubscriptionHistory(ctx context.Context, userID int64, limit, offset int32) ([]domain.UserSubscription, error) {
	subs, err := s.queries.GetUserSubscriptionHistory(ctx, dbgen.GetUserSubscriptionHistoryParams{
		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.UserSubscription, len(subs))
	for i, sub := range subs {
		result[i] = domain.UserSubscription{
			ID:               sub.ID,
			UserID:           sub.UserID,
			PlanID:           sub.PlanID,
			StartsAt:         sub.StartsAt.Time,
			ExpiresAt:        sub.ExpiresAt.Time,
			Status:           sub.Status,
			PaymentReference: fromPgText(sub.PaymentReference),
			PaymentMethod:    fromPgText(sub.PaymentMethod),
			AutoRenew:        sub.AutoRenew,
			CancelledAt:      timePtr(sub.CancelledAt),
			CreatedAt:        sub.CreatedAt.Time,
			UpdatedAt:        timePtr(sub.UpdatedAt),
			PlanName:         &sub.PlanName,
			DurationValue:    &sub.DurationValue,
			DurationUnit:     &sub.DurationUnit,
			Price:            float64Ptr(fromPgNumeric(sub.Price)),
			Currency:         &sub.Currency,
		}
	}
	return result, nil
}

func (s *Store) HasActiveSubscription(ctx context.Context, userID int64) (bool, error) {
	return s.queries.HasActiveSubscription(ctx, userID)
}

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

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

func (s *Store) UpdateAutoRenew(ctx context.Context, id int64, autoRenew bool) error {
	return s.queries.UpdateAutoRenew(ctx, dbgen.UpdateAutoRenewParams{
		AutoRenew: autoRenew,
		ID:        id,
	})
}

func (s *Store) ExtendSubscription(ctx context.Context, id int64, newExpiresAt time.Time) error {
	return s.queries.ExtendSubscription(ctx, dbgen.ExtendSubscriptionParams{
		ExpiresAt: toPgTimestamptz(newExpiresAt),
		ID:        id,
	})
}

// Helper conversion functions

func subscriptionPlanToDomain(p dbgen.SubscriptionPlan) *domain.SubscriptionPlan {
	return &domain.SubscriptionPlan{
		ID:            p.ID,
		Name:          p.Name,
		Description:   fromPgText(p.Description),
		DurationValue: p.DurationValue,
		DurationUnit:  p.DurationUnit,
		Price:         fromPgNumeric(p.Price),
		Currency:      p.Currency,
		IsActive:      p.IsActive,
		CreatedAt:     p.CreatedAt.Time,
		UpdatedAt:     timePtr(p.UpdatedAt),
	}
}

func userSubscriptionToDomain(s dbgen.UserSubscription) *domain.UserSubscription {
	return &domain.UserSubscription{
		ID:               s.ID,
		UserID:           s.UserID,
		PlanID:           s.PlanID,
		StartsAt:         s.StartsAt.Time,
		ExpiresAt:        s.ExpiresAt.Time,
		Status:           s.Status,
		PaymentReference: fromPgText(s.PaymentReference),
		PaymentMethod:    fromPgText(s.PaymentMethod),
		AutoRenew:        s.AutoRenew,
		CancelledAt:      timePtr(s.CancelledAt),
		CreatedAt:        s.CreatedAt.Time,
		UpdatedAt:        timePtr(s.UpdatedAt),
	}
}

func userSubscriptionWithPlanToDomain(s dbgen.GetUserSubscriptionByIDRow) *domain.UserSubscription {
	return &domain.UserSubscription{
		ID:               s.ID,
		UserID:           s.UserID,
		PlanID:           s.PlanID,
		StartsAt:         s.StartsAt.Time,
		ExpiresAt:        s.ExpiresAt.Time,
		Status:           s.Status,
		PaymentReference: fromPgText(s.PaymentReference),
		PaymentMethod:    fromPgText(s.PaymentMethod),
		AutoRenew:        s.AutoRenew,
		CancelledAt:      timePtr(s.CancelledAt),
		CreatedAt:        s.CreatedAt.Time,
		UpdatedAt:        timePtr(s.UpdatedAt),
		PlanName:         &s.PlanName,
		DurationValue:    &s.DurationValue,
		DurationUnit:     &s.DurationUnit,
		Price:            float64Ptr(fromPgNumeric(s.Price)),
		Currency:         &s.Currency,
	}
}

func stringVal(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

func int32Val(i *int32) int32 {
	if i == nil {
		return 0
	}
	return *i
}

func numericPtrToNumeric(val *float64) pgtype.Numeric {
	if val == nil {
		return pgtype.Numeric{Valid: false}
	}
	return toPgNumeric(*val)
}

func boolPtrToBool(b *bool) bool {
	if b == nil {
		return false
	}
	return *b
}

func float64Ptr(f float64) *float64 {
	return &f
}
