// This file is part of taler-mailbox, the Taler Mailbox implementation.
// Copyright (C) 2026 Martin Schanzenbach
//
// taler-mailbox is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// taler-mailbox is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL3.0-or-later

package mailbox

import (
	"context"
	"database/sql"
	"errors"
	_ "github.com/lib/pq"
	"time"
)

type Timestamp struct {
	Seconds uint64 `json:"t_s"`
}

type MailboxMetadata struct {
	// ORM
	Serial int64 `json:"-"`

	// ORM helper hash of signing key
	HashedSigningKey string `json:"-"`

	// The mailbox signing key.
	// Note that $H_MAILBOX == H(singingKey).
	// Note also how this key cannot be updated
	// as it identifies the mailbox.
	SigningKey string `json:"signing_key"`

	// Type of key.
	// Optional, as currently only
	// EdDSA keys are supported.
	SigningKeyType string `json:"signing_key_type"`

	// The mailbox encryption key.
	// This is an HPKE public key
	// in the X25519 format for use
	// in a X25519-DHKEM (RFC 9180).
	// Base32 crockford-encoded.
	EncryptionKey string `json:"encryption_key"`

	// Type of key.
	// Optional, as currently only
	// X25519 keys are supported.
	EncryptionKeyType string `json:"encryption_key_type"`

	// Expiration of this mapping (UNIX Epoch seconds).
	Expiration Timestamp `json:"expiration"`

	// Info field (e.g for Keyoxide claim proof)
	Info string `json:"info,omitempty"`
}

type PendingMailboxRegistration struct {
	// ORM
	Serial int64 `json:"-"`

	// Created timestamp (in Seconds / UNIX Epoch)
	CreatedAt int64

	// Hash of the inbox for this entry
	HashedSigningKey string // Requested registration duration

	// Registration duration (in Seconds)
	Duration int64

	// The order ID associated with this registration
	OrderID string `json:"-"`
}

type InboxEntry struct {
	// ORM
	Serial int64 `json:"-"`

	// Encrypted message. Must be exactly 256-32 bytes long.
	Body []byte

	// Hash of the inbox for this entry
	HashedSigningKey string
}

func InsertInboxEntryIntoDatabase(db *sql.DB, e *InboxEntry) error {
	query := `INSERT INTO taler_mailbox.inbox_entries
						VALUES (DEFAULT, $1, $2);`
	rows, err := db.Query(query, e.HashedSigningKey, e.Body)
	if err != nil {
		return err
	}
	defer rows.Close()
	return nil
}

func InsertPendingRegistrationIntoDatabase(db *sql.DB, pr *PendingMailboxRegistration) error {
	pr.CreatedAt = time.Now().Unix()
	query := `INSERT INTO taler_mailbox.pending_mailbox_registrations
						VALUES (DEFAULT, $1, $2, $3, $4);`
	rows, err := db.Query(query, pr.CreatedAt, pr.HashedSigningKey, pr.OrderID, pr.Duration)
	if err != nil {
		return err
	}
	defer rows.Close()
	return nil
}

func InsertMailboxRegistrationIntoDatabase(db *sql.DB, mb *MailboxMetadata) error {
	query := `INSERT INTO taler_mailbox.mailbox_metadata
						VALUES (DEFAULT, $1, $2, $3, $4, $5, $6, $7);`
	rows, err := db.Query(query, mb.HashedSigningKey, mb.SigningKey, mb.SigningKeyType, mb.EncryptionKey, mb.EncryptionKeyType, mb.Expiration.Seconds, mb.Info)
	if err != nil {
		return err
	}
	defer rows.Close()
	return nil
}

func UpdatePendingRegistrationOrderIdInDatabase(db *sql.DB, pr *PendingMailboxRegistration) error {
	query := `UPDATE taler_mailbox.pending_mailbox_registrations
						SET
								"order_id" = $2
						WHERE "hashed_signing_key" = $1;`
	rows, err := db.Query(query, pr.HashedSigningKey, pr.OrderID)
	if err != nil {
		return err
	}
	defer rows.Close()
	return nil
}

func UpdateMailboxExpirationInDatabase(db *sql.DB, mb *MailboxMetadata) error {
	query := `UPDATE taler_mailbox.mailbox_metadata
						SET
								"expiration" = $2
						WHERE "hashed_signing_key" = $1;`
	rows, err := db.Query(query, mb.HashedSigningKey, mb.Expiration.Seconds)
	if err != nil {
		return err
	}
	defer rows.Close()
	return nil
}

func GetPendingRegistrationFromDatabaseBySigningKey(db *sql.DB, pr *PendingMailboxRegistration, hashedKey string) error {
	query := `SELECT
								"serial",
								"hashed_signing_key",
								"registration_duration",
								"order_id"
				FROM taler_mailbox.pending_mailbox_registrations
				WHERE
					"hashed_signing_key"=$1
				LIMIT 1
				;`
	// Execute Query
	rows, err := db.Query(query, hashedKey)
	if err != nil {
		return err
	}
	defer rows.Close()
	// Iterate over first
	if !rows.Next() {
		return errors.New("mailbox metadata does not exist")
	}
	return rows.Scan(
		&pr.Serial,
		&pr.HashedSigningKey,
		&pr.Duration,
		&pr.OrderID,
	)
}

func GetMailboxMetadataFromDatabaseBySigningKey(db *sql.DB, mb *MailboxMetadata, hashedKey string) error {
	query := `SELECT
								"serial",
								"hashed_signing_key",
								"signing_key",
								"signing_key_type",
								"encryption_key",
								"encryption_key_type",
								"expiration",
								"info"
				FROM taler_mailbox.mailbox_metadata
				WHERE
					"hashed_signing_key"=$1
				LIMIT 1
				;`
	// Execute Query
	rows, err := db.Query(query, hashedKey)
	if err != nil {
		return err
	}
	defer rows.Close()
	// Iterate over first
	if !rows.Next() {
		return errors.New("Mailbox metadata does not exist")
	}
	return rows.Scan(
		&mb.Serial,
		&mb.HashedSigningKey,
		&mb.SigningKey,
		&mb.SigningKeyType,
		&mb.EncryptionKey,
		&mb.EncryptionKeyType,
		&mb.Expiration.Seconds,
		&mb.Info,
	)
}

func GetInboxEntryFromDatabaseBySigningKeyAndBody(db *sql.DB, e *InboxEntry, hashedKey string, body []byte) error {
	query := `SELECT
								"serial",
								"hashed_signing_key",
								"body"
				FROM taler_mailbox.inbox_entries
				WHERE
					"hashed_signing_key"=$1 AND
					"body"=$2
				;`
	// Execute Query
	rows, err := db.Query(query, hashedKey, body)
	if err != nil {
		return err
	}
	defer rows.Close()
	// Iterate over first
	if !rows.Next() {
		return errors.New("inbox entry does not exist")
	}
	return rows.Scan(
		&e.Serial,
		&e.HashedSigningKey,
		&e.Body,
	)
}

func GetInboxEntryFromDatabaseBySerial(db *sql.DB, e *InboxEntry, hashedKey string, serial int64) error {
	query := `SELECT
								"serial",
								"hashed_signing_key",
								"body"
				FROM taler_mailbox.inbox_entries
				WHERE
					"serial"=$1 AND
					"hashed_signing_key"=$2
				;`
	// Execute Query
	rows, err := db.Query(query, serial, hashedKey)
	if err != nil {
		return err
	}
	defer rows.Close()
	// Iterate over first
	if !rows.Next() {
		return errors.New("inbox entry does not exist")
	}
	return rows.Scan(
		&e.Serial,
		&e.HashedSigningKey,
		&e.Body,
	)
}

func DeletePendingRegistrationFromDatabase(db *sql.DB, pr *PendingMailboxRegistration) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
				FROM taler_mailbox.pending_mailbox_registrations
				WHERE
					"serial" = $1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query, pr.Serial)
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

// DeleteInboxEntryFromDatabaseBySerial Deletes all entries starting from given serial
func DeleteInboxEntryFromDatabaseBySerial(db *sql.DB, e *InboxEntry, count int) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE FROM taler_mailbox.inbox_entries
				WHERE serial IN (
					SELECT serial FROM taler_mailbox.inbox_entries
					WHERE
						"hashed_signing_key"=$1 AND
						"serial">=$2
					LIMIT $3
				)
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query, e.HashedSigningKey, e.Serial, count)
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

// DeleteStaleRegstrationsFromDatabase purges stale registrations
func DeleteStaleRegistrationsFromDatabase(db *sql.DB, registrationExpiration time.Time) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
	FROM taler_mailbox.mailbox_metadata
				WHERE
					"expiration" < $1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query, registrationExpiration.Unix())
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

// DeleteStalePendingRegstrationsFromDatabase purges stale registrations
func DeleteStalePendingRegistrationsFromDatabase(db *sql.DB, registrationExpiration time.Time) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
	FROM taler_mailbox.pending_mailbox_registrations
				WHERE
					"created_at" < $1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query, registrationExpiration.Unix())
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

func DeleteAllPendingRegistrationsFromDatabase(db *sql.DB) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
				FROM taler_mailbox.pending_mailbox_registrations
				WHERE
					1=1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query)
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

func DeleteAllMailboxesFromDatabase(db *sql.DB) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
				FROM taler_mailbox.mailbox_metadata
				WHERE
					1=1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query)
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}

func DeleteAllInboxEntriesFromDatabase(db *sql.DB) (int64, error) {
	var ctx context.Context
	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, err
	}
	defer conn.Close()
	query := `DELETE
				FROM taler_mailbox.inbox_entries
				WHERE
					1=1
				;`
	// Execute Query
	result, err := conn.ExecContext(ctx, query)
	if err != nil {
		return 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return rows, nil
}
