package db

import (
	"database/sql"
	"errors"
	"time"
)

// CreateSMIMEIdentity adds a new S/MIME identity for a mailbox — a mailbox may hold
// several at once (see esrv_mailbox_smime_identities in schema.go).
func (d *DB) CreateSMIMEIdentity(mailboxID int64, certPEM, keyPEM string, notAfter time.Time) (int64, error) {
	res, err := d.Exec(`
		INSERT INTO esrv_mailbox_smime_identities (mailbox_id, cert_pem, key_pem, not_after)
		VALUES (?, ?, ?, ?)
	`, mailboxID, certPEM, keyPEM, notAfter)
	if err != nil {
		return 0, err
	}
	return res.LastInsertId()
}

// ListSMIMEIdentities returns a mailbox's S/MIME identities, most recent first.
func (d *DB) ListSMIMEIdentities(mailboxID int64) ([]MailboxSMIMEIdentity, error) {
	rows, err := d.Query(`SELECT id, mailbox_id, cert_pem, key_pem, not_after, created_at
		FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? ORDER BY created_at DESC, id DESC`, mailboxID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []MailboxSMIMEIdentity
	for rows.Next() {
		var id MailboxSMIMEIdentity
		if err := rows.Scan(&id.ID, &id.MailboxID, &id.CertPEM, &id.KeyPEM, &id.NotAfter, &id.CreatedAt); err != nil {
			return nil, err
		}
		out = append(out, id)
	}
	return out, rows.Err()
}

// GetSMIMEIdentity returns nil, nil if no such identity exists for this mailbox —
// scoped to mailboxID so one mailbox owner can't reach another's identity by
// guessing its ID.
func (d *DB) GetSMIMEIdentity(mailboxID, identityID int64) (*MailboxSMIMEIdentity, error) {
	row := d.QueryRow(`SELECT id, mailbox_id, cert_pem, key_pem, not_after, created_at
		FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID)
	var id MailboxSMIMEIdentity
	if err := row.Scan(&id.ID, &id.MailboxID, &id.CertPEM, &id.KeyPEM, &id.NotAfter, &id.CreatedAt); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}
	return &id, nil
}

// DeleteSMIMEIdentity removes one identity, scoped to mailboxID.
func (d *DB) DeleteSMIMEIdentity(mailboxID, identityID int64) error {
	_, err := d.Exec(`DELETE FROM esrv_mailbox_smime_identities WHERE mailbox_id = ? AND id = ?`, mailboxID, identityID)
	return err
}

// UpsertSMIMEContact adds a contact certificate, replacing any existing certificate
// already on file for that email (e.g. after the contact renews their cert).
func (d *DB) UpsertSMIMEContact(mailboxID int64, email, certPEM string) error {
	_, err := d.Exec(`
		INSERT INTO esrv_mailbox_smime_contacts (mailbox_id, email, cert_pem)
		VALUES (?, ?, ?)
		ON CONFLICT(mailbox_id, email) DO UPDATE SET cert_pem = excluded.cert_pem
	`, mailboxID, email, certPEM)
	return err
}

// GetSMIMEContact returns nil, nil if no certificate is on file for that email.
func (d *DB) GetSMIMEContact(mailboxID int64, email string) (*MailboxSMIMEContact, error) {
	row := d.QueryRow(`SELECT id, mailbox_id, email, cert_pem, created_at FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? AND email = ?`, mailboxID, email)
	var c MailboxSMIMEContact
	if err := row.Scan(&c.ID, &c.MailboxID, &c.Email, &c.CertPEM, &c.CreatedAt); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}
	return &c, nil
}

// ListSMIMEContacts returns a mailbox's collected contact certificates, alphabetical
// by email.
func (d *DB) ListSMIMEContacts(mailboxID int64) ([]MailboxSMIMEContact, error) {
	rows, err := d.Query(`SELECT id, mailbox_id, email, cert_pem, created_at FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? ORDER BY email`, mailboxID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []MailboxSMIMEContact
	for rows.Next() {
		var c MailboxSMIMEContact
		if err := rows.Scan(&c.ID, &c.MailboxID, &c.Email, &c.CertPEM, &c.CreatedAt); err != nil {
			return nil, err
		}
		out = append(out, c)
	}
	return out, rows.Err()
}

// DeleteSMIMEContact removes one contact certificate, scoped to mailboxID so one
// mailbox owner can't delete another's contact by guessing its ID.
func (d *DB) DeleteSMIMEContact(mailboxID, contactID int64) error {
	_, err := d.Exec(`DELETE FROM esrv_mailbox_smime_contacts WHERE mailbox_id = ? AND id = ?`, mailboxID, contactID)
	return err
}
