package postgres

import (
	"context"
	"database/sql"
	"errors"

	"github.com/google/uuid"
	"github.com/lib/pq"
	"github.com/rwadurian/mpc-system/services/account/domain/entities"
	"github.com/rwadurian/mpc-system/services/account/domain/repositories"
	"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)

// AccountPostgresRepo implements AccountRepository using PostgreSQL
type AccountPostgresRepo struct {
	db *sql.DB
}

// NewAccountPostgresRepo creates a new AccountPostgresRepo
func NewAccountPostgresRepo(db *sql.DB) repositories.AccountRepository {
	return &AccountPostgresRepo{db: db}
}

// Create creates a new account
func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Account) error {
	query := `
		INSERT INTO accounts (id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
	`

	_, err := r.db.ExecContext(ctx, query,
		account.ID.UUID(),
		account.Username,
		account.Email,
		account.Phone,
		account.PublicKey,
		account.KeygenSessionID,
		account.ThresholdN,
		account.ThresholdT,
		account.Status.String(),
		account.CreatedAt,
		account.UpdatedAt,
		account.LastLoginAt,
		pq.Array(account.SigningParties),
	)

	return err
}

// GetByID retrieves an account by ID
func (r *AccountPostgresRepo) GetByID(ctx context.Context, id value_objects.AccountID) (*entities.Account, error) {
	query := `
		SELECT id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
		FROM accounts
		WHERE id = $1
	`

	return r.scanAccount(r.db.QueryRowContext(ctx, query, id.UUID()))
}

// GetByUsername retrieves an account by username
func (r *AccountPostgresRepo) GetByUsername(ctx context.Context, username string) (*entities.Account, error) {
	query := `
		SELECT id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
		FROM accounts
		WHERE username = $1
	`

	return r.scanAccount(r.db.QueryRowContext(ctx, query, username))
}

// GetByEmail retrieves an account by email
func (r *AccountPostgresRepo) GetByEmail(ctx context.Context, email string) (*entities.Account, error) {
	query := `
		SELECT id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
		FROM accounts
		WHERE email = $1
	`

	return r.scanAccount(r.db.QueryRowContext(ctx, query, email))
}

// GetByPublicKey retrieves an account by public key
func (r *AccountPostgresRepo) GetByPublicKey(ctx context.Context, publicKey []byte) (*entities.Account, error) {
	query := `
		SELECT id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
		FROM accounts
		WHERE public_key = $1
	`

	return r.scanAccount(r.db.QueryRowContext(ctx, query, publicKey))
}

// Update updates an existing account
func (r *AccountPostgresRepo) Update(ctx context.Context, account *entities.Account) error {
	query := `
		UPDATE accounts
		SET username = $2, email = $3, phone = $4, public_key = $5, keygen_session_id = $6,
			threshold_n = $7, threshold_t = $8, status = $9, updated_at = $10, last_login_at = $11,
			signing_parties = $12
		WHERE id = $1
	`

	result, err := r.db.ExecContext(ctx, query,
		account.ID.UUID(),
		account.Username,
		account.Email,
		account.Phone,
		account.PublicKey,
		account.KeygenSessionID,
		account.ThresholdN,
		account.ThresholdT,
		account.Status.String(),
		account.UpdatedAt,
		account.LastLoginAt,
		pq.Array(account.SigningParties),
	)

	if err != nil {
		return err
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}

	if rowsAffected == 0 {
		return entities.ErrAccountNotFound
	}

	return nil
}

// Delete deletes an account
func (r *AccountPostgresRepo) Delete(ctx context.Context, id value_objects.AccountID) error {
	query := `DELETE FROM accounts WHERE id = $1`

	result, err := r.db.ExecContext(ctx, query, id.UUID())
	if err != nil {
		return err
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}

	if rowsAffected == 0 {
		return entities.ErrAccountNotFound
	}

	return nil
}

// ExistsByUsername checks if username exists
func (r *AccountPostgresRepo) ExistsByUsername(ctx context.Context, username string) (bool, error) {
	query := `SELECT EXISTS(SELECT 1 FROM accounts WHERE username = $1)`

	var exists bool
	err := r.db.QueryRowContext(ctx, query, username).Scan(&exists)
	return exists, err
}

// ExistsByEmail checks if email exists
func (r *AccountPostgresRepo) ExistsByEmail(ctx context.Context, email string) (bool, error) {
	query := `SELECT EXISTS(SELECT 1 FROM accounts WHERE email = $1)`

	var exists bool
	err := r.db.QueryRowContext(ctx, query, email).Scan(&exists)
	return exists, err
}

// List lists accounts with pagination
func (r *AccountPostgresRepo) List(ctx context.Context, offset, limit int) ([]*entities.Account, error) {
	query := `
		SELECT id, username, email, phone, public_key, keygen_session_id,
			threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
		FROM accounts
		ORDER BY created_at DESC
		LIMIT $1 OFFSET $2
	`

	rows, err := r.db.QueryContext(ctx, query, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var accounts []*entities.Account
	for rows.Next() {
		account, err := r.scanAccountFromRows(rows)
		if err != nil {
			return nil, err
		}
		accounts = append(accounts, account)
	}

	return accounts, rows.Err()
}

// Count returns the total number of accounts
func (r *AccountPostgresRepo) Count(ctx context.Context) (int64, error) {
	query := `SELECT COUNT(*) FROM accounts`

	var count int64
	err := r.db.QueryRowContext(ctx, query).Scan(&count)
	return count, err
}

// scanAccount scans a single account row
func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, error) {
	var (
		id              uuid.UUID
		username        string
		email           sql.NullString
		phone           sql.NullString
		publicKey       []byte
		keygenSessionID uuid.UUID
		thresholdN      int
		thresholdT      int
		status          string
		signingParties  pq.StringArray
		account         entities.Account
	)

	err := row.Scan(
		&id,
		&username,
		&email,
		&phone,
		&publicKey,
		&keygenSessionID,
		&thresholdN,
		&thresholdT,
		&status,
		&account.CreatedAt,
		&account.UpdatedAt,
		&account.LastLoginAt,
		&signingParties,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, entities.ErrAccountNotFound
		}
		return nil, err
	}

	account.ID = value_objects.AccountIDFromUUID(id)
	account.Username = username
	if email.Valid {
		account.Email = &email.String
	}
	if phone.Valid {
		account.Phone = &phone.String
	}
	account.PublicKey = publicKey
	account.KeygenSessionID = keygenSessionID
	account.ThresholdN = thresholdN
	account.ThresholdT = thresholdT
	account.Status = value_objects.AccountStatus(status)
	account.SigningParties = signingParties

	return &account, nil
}

// scanAccountFromRows scans account from rows
func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Account, error) {
	var (
		id              uuid.UUID
		username        string
		email           sql.NullString
		phone           sql.NullString
		publicKey       []byte
		keygenSessionID uuid.UUID
		thresholdN      int
		thresholdT      int
		status          string
		signingParties  pq.StringArray
		account         entities.Account
	)

	err := rows.Scan(
		&id,
		&username,
		&email,
		&phone,
		&publicKey,
		&keygenSessionID,
		&thresholdN,
		&thresholdT,
		&status,
		&account.CreatedAt,
		&account.UpdatedAt,
		&account.LastLoginAt,
		&signingParties,
	)

	if err != nil {
		return nil, err
	}

	account.ID = value_objects.AccountIDFromUUID(id)
	account.Username = username
	if email.Valid {
		account.Email = &email.String
	}
	if phone.Valid {
		account.Phone = &phone.String
	}
	account.PublicKey = publicKey
	account.KeygenSessionID = keygenSessionID
	account.ThresholdN = thresholdN
	account.ThresholdT = thresholdT
	account.Status = value_objects.AccountStatus(status)
	account.SigningParties = signingParties

	return &account, nil
}
