package models

import (
	"context"
	"errors"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/kjannette/koin-ping/backend/internal/domain"
)

type UserModel struct {
	pool *pgxpool.Pool
}

func NewUserModel(pool *pgxpool.Pool) *UserModel {
	return &UserModel{pool: pool}
}

const userColumns = `id, firebase_uid, email, display_name,
	stripe_customer_id, stripe_subscription_id, subscription_status,
	subscription_tier, subscription_created_at, created_at, updated_at`

func scanUser(row pgx.Row) (*domain.User, error) {
	var u domain.User
	err := row.Scan(
		&u.ID, &u.FirebaseUID, &u.Email, &u.DisplayName,
		&u.StripeCustomerID, &u.StripeSubscriptionID, &u.SubscriptionStatus,
		&u.SubscriptionTier, &u.SubscriptionCreatedAt, &u.CreatedAt, &u.UpdatedAt,
	)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil //nolint:nilnil
		}
		return nil, err
	}
	return &u, nil
}

func (m *UserModel) getByFirebaseUID(ctx context.Context, firebaseUID string) (*domain.User, error) {
	row := m.pool.QueryRow(ctx,
		`SELECT `+userColumns+` FROM users WHERE firebase_uid = $1`,
		firebaseUID,
	)
	return scanUser(row)
}

func (m *UserModel) getByEmail(ctx context.Context, email string) (*domain.User, error) {
	row := m.pool.QueryRow(ctx,
		`SELECT `+userColumns+` FROM users WHERE email = $1`,
		email,
	)
	return scanUser(row)
}

func isUniqueViolation(err error) bool {
	var pgErr *pgconn.PgError
	if errors.As(err, &pgErr) {
		return pgErr.Code == "23505"
	}
	return false
}

// FindOrCreateByFirebaseUID returns the local user for a Firebase UID,
// creating one if it doesn't exist yet.
//
// For onboarding races or legacy duplicate-identity states, this method
// gracefully falls back to an existing row by email instead of failing
// with a unique-email violation.
func (m *UserModel) FindOrCreateByFirebaseUID(ctx context.Context, firebaseUID, email string) (*domain.User, error) {
	existingByUID, err := m.getByFirebaseUID(ctx, firebaseUID)
	if err != nil {
		return nil, err
	}
	if existingByUID != nil {
		return existingByUID, nil
	}

	if email != "" {
		existingByEmail, err := m.getByEmail(ctx, email)
		if err != nil {
			return nil, err
		}
		if existingByEmail != nil {
			return existingByEmail, nil
		}
	}

	row := m.pool.QueryRow(ctx,
		`INSERT INTO users (firebase_uid, email)
		 VALUES ($1, $2)
		 ON CONFLICT (firebase_uid) DO UPDATE SET updated_at = NOW()
		 RETURNING `+userColumns,
		firebaseUID, email,
	)
	user, err := scanUser(row)
	if err == nil {
		return user, nil
	}

	// If a concurrent request inserted by email or firebase_uid first,
	// read the existing record and continue without surfacing a 500.
	if isUniqueViolation(err) {
		existingByUID, readErr := m.getByFirebaseUID(ctx, firebaseUID)
		if readErr != nil {
			return nil, readErr
		}
		if existingByUID != nil {
			return existingByUID, nil
		}
		if email != "" {
			existingByEmail, readErr := m.getByEmail(ctx, email)
			if readErr != nil {
				return nil, readErr
			}
			if existingByEmail != nil {
				return existingByEmail, nil
			}
		}
	}

	return nil, err
}

func (m *UserModel) GetByID(ctx context.Context, id string) (*domain.User, error) {
	row := m.pool.QueryRow(ctx,
		`SELECT `+userColumns+` FROM users WHERE id = $1`, id,
	)
	return scanUser(row)
}

func (m *UserModel) UpdateStripeCustomer(ctx context.Context, userID, stripeCustomerID string) error {
	_, err := m.pool.Exec(ctx,
		`UPDATE users SET stripe_customer_id = $2, updated_at = NOW() WHERE id = $1`,
		userID, stripeCustomerID,
	)
	return err
}

func (m *UserModel) ActivateSubscription(ctx context.Context, stripeCustomerID, subscriptionID, status string, tier domain.SubscriptionTier) error {
	_, err := m.pool.Exec(ctx,
		`UPDATE users
		 SET stripe_subscription_id = $2,
		     subscription_status = $3,
		     subscription_tier = $4,
		     subscription_created_at = COALESCE(subscription_created_at, NOW()),
		     updated_at = NOW()
		 WHERE stripe_customer_id = $1`,
		stripeCustomerID, subscriptionID, status, string(tier),
	)
	return err
}

func (m *UserModel) UpdateSubscriptionTier(ctx context.Context, userID string, tier domain.SubscriptionTier) error {
	_, err := m.pool.Exec(ctx,
		`UPDATE users SET subscription_tier = $2, updated_at = NOW() WHERE id = $1`,
		userID, string(tier),
	)
	return err
}

func (m *UserModel) ActivateFreeTier(ctx context.Context, userID string) error {
	_, err := m.pool.Exec(ctx,
		`UPDATE users
		 SET subscription_status = 'active',
		     subscription_tier = 'free',
		     subscription_created_at = COALESCE(subscription_created_at, NOW()),
		     updated_at = NOW()
		 WHERE id = $1`,
		userID,
	)
	return err
}

func (m *UserModel) UpdateSubscriptionStatus(ctx context.Context, stripeCustomerID, status string) error {
	_, err := m.pool.Exec(ctx,
		`UPDATE users SET subscription_status = $2, updated_at = NOW()
		 WHERE stripe_customer_id = $1`,
		stripeCustomerID, status,
	)
	return err
}
