package data

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"math/big"
	"time"

	"github.com/lib/pq"
)

type BlindSign struct {
	UserID int64 `json:"user_id"`
	IssueID int64 `json:"issue_id"`
	Created time.Time `json:"created"`
}

type BlindSignModel struct {
	DB *sql.DB
}

func (m BlindSignModel) Get(userID int64, issueID int64) (*BlindSign, error) {
	query :=
`SELECT user_id, issue_id, created
FROM blind_signs
WHERE user_id = $1 AND issue_id = $2`

	args := []interface{}{
		userID,
		issueID,
	}

	var blindSign BlindSign

	err := m.DB.QueryRow(query, args...).Scan(&blindSign.UserID, &blindSign.IssueID, &blindSign.Created)

	if err != nil {
		switch {
		case errors.Is(err, sql.ErrNoRows):
			return nil, ErrRecordNotFound
		default:
			return nil, err
		}
	}

	return &blindSign, nil
}

func (m BlindSignModel) Insert(blind_sign *BlindSign) error {
	query := `
INSERT INTO blind_signs (user_id, issue_id)
VALUES ($1, $2)
RETURNING created`

	args := []interface{}{
		blind_sign.UserID,
		blind_sign.IssueID,
	}

	err := m.DB.QueryRow(query, args...).Scan(&blind_sign.Created)
	if pgErr, ok := err.(*pq.Error); ok {
		if pgErr.Code == "23505" {
			return ErrDuplicateBlindSign
		}
	}
	return err
}

func (m BlindSignModel) BlindSign(issueID int64, blindedVoteBytes []byte) ([]byte, error) {
	if issueID < 1 {
		return nil, ErrRecordNotFound
	}

	query := `SELECT rsa_private_pem FROM issues WHERE id = $1`
 
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()
 
	var pemBytes []byte
	err := m.DB.QueryRowContext(ctx, query, issueID).Scan(&pemBytes)
	if err != nil {
		switch {
		case errors.Is(err, sql.ErrNoRows):
			return nil, ErrRecordNotFound
		default:
			return nil, err
		}
	}
 
	key, err := parsePrivateKey(pemBytes)
	if err != nil {
		return nil, fmt.Errorf("parse private key: %w", err)
	}
 
	m_ := new(big.Int).SetBytes(blindedVoteBytes)
 
	// Validate range: m′ must be in [1, n-1]
	one := big.NewInt(1)
	if m_.Cmp(one) < 0 || m_.Cmp(key.N) >= 0 {
		return nil, ErrInvalidBlindedVote
	}
 
	// s′ = m′^d mod n
	sig := new(big.Int).Exp(m_, key.D, key.N)
 
	return sig.Bytes(), nil
}
