package layoutroutingsecurity

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

	"github.com/go-sql-driver/mysql"
	layoutroutingsecurityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutroutingsecurityv1"
)

const (
	canonicalViewInsertTemplate = `INSERT INTO %s (
OrgId, LayoutRoutingSecurityViewDigest, CanonicalViewBytes)
VALUES (?, ?, ?)`
	canonicalViewReadTemplate = `SELECT CanonicalViewBytes
FROM %s
WHERE OrgId = ? AND LayoutRoutingSecurityViewDigest = ?`
)

// BeginTx begins a transaction through this verified store. The returned
// capability never exposes the raw *sql.Tx, so only this store can accept it
// for canonical-view writes.
func (store *VerifiedViewStore) BeginTx(
	ctx context.Context,
	options *sql.TxOptions,
) (*VerifiedViewStoreTx, error) {
	db, _, err := requireVerifiedViewStore(store)
	if err != nil {
		return nil, err
	}
	tx, err := db.BeginTx(ctx, options)
	if err != nil {
		return nil, err
	}
	return &VerifiedViewStoreTx{store: store, tx: tx}, nil
}

// StoreCanonicalView inserts one immutable canonical view under the
// contract-owned digest derived from the typed view. The transaction was
// created by a verified view store and is never committed or rolled back here.
// A duplicate key is idempotent only when the already-stored bytes are exactly
// equal.
func (transaction *VerifiedViewStoreTx) StoreCanonicalView(
	ctx context.Context,
	view layoutroutingsecurityv1.View,
) (string, error) {
	tx, table, err := requireVerifiedViewStoreTx(transaction)
	if err != nil {
		return "", err
	}

	canonical, err := layoutroutingsecurityv1.CanonicalViewBytes(view)
	if err != nil {
		return "", err
	}
	digest, err := layoutroutingsecurityv1.ViewDigest(view)
	if err != nil {
		return "", err
	}

	if _, err := tx.ExecContext(
		ctx,
		canonicalViewInsertSQL(table),
		view.OrgID,
		digest,
		canonical,
	); err == nil {
		return digest, nil
	} else if !isDuplicateKey(err) {
		return "", err
	}

	var stored []byte
	if err := tx.QueryRowContext(
		ctx,
		canonicalViewReadForUpdateSQL(table),
		view.OrgID,
		digest,
	).Scan(&stored); err != nil {
		return "", fmt.Errorf("layout routing security: duplicate insert readback: %w", err)
	}
	if !bytes.Equal(stored, canonical) {
		return "", fmt.Errorf("%w: existing bytes differ", ErrViewDigestCollision)
	}
	return digest, nil
}

// Commit completes a transaction created by a verified view store.
func (transaction *VerifiedViewStoreTx) Commit() error {
	tx, _, err := requireVerifiedViewStoreTx(transaction)
	if err != nil {
		return err
	}
	return tx.Commit()
}

// Rollback abandons a transaction created by a verified view store.
func (transaction *VerifiedViewStoreTx) Rollback() error {
	tx, _, err := requireVerifiedViewStoreTx(transaction)
	if err != nil {
		return err
	}
	return tx.Rollback()
}

// ReadCanonicalView recovers and strictly decodes the immutable artifact for
// one exact org and digest. It is a persistence primitive only: it does not
// select a current view or register a serving read path.
func ReadCanonicalView(
	ctx context.Context,
	store *VerifiedViewStore,
	orgID string,
	digest string,
) (layoutroutingsecurityv1.View, error) {
	db, table, err := requireVerifiedViewStore(store)
	if err != nil {
		return layoutroutingsecurityv1.View{}, err
	}

	var canonical []byte
	if err := db.QueryRowContext(
		ctx,
		canonicalViewReadSQL(table),
		orgID,
		digest,
	).Scan(&canonical); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return layoutroutingsecurityv1.View{}, fmt.Errorf("%w: exact org and digest", ErrViewNotFound)
		}
		return layoutroutingsecurityv1.View{}, err
	}

	view, err := layoutroutingsecurityv1.ParseCanonicalView(canonical)
	if err != nil {
		return layoutroutingsecurityv1.View{}, fmt.Errorf("layout routing security: parse stored canonical view: %w", err)
	}
	if view.OrgID != orgID {
		return layoutroutingsecurityv1.View{}, fmt.Errorf("layout routing security: stored canonical view org does not match requested org")
	}
	actualDigest, err := layoutroutingsecurityv1.ViewDigest(view)
	if err != nil {
		return layoutroutingsecurityv1.View{}, err
	}
	if actualDigest != digest {
		return layoutroutingsecurityv1.View{}, fmt.Errorf("layout routing security: stored canonical view digest does not match requested digest")
	}
	return view, nil
}

func canonicalViewInsertSQL(table *tableIdentity) string {
	return fmt.Sprintf(canonicalViewInsertTemplate, table.name)
}

func canonicalViewReadSQL(table *tableIdentity) string {
	return fmt.Sprintf(canonicalViewReadTemplate, table.name)
}

func canonicalViewReadForUpdateSQL(table *tableIdentity) string {
	return canonicalViewReadSQL(table) + " FOR UPDATE"
}

func isDuplicateKey(err error) bool {
	var mysqlError *mysql.MySQLError
	return errors.As(err, &mysqlError) && mysqlError.Number == 1062
}

func requireVerifiedViewStoreTx(transaction *VerifiedViewStoreTx) (*sql.Tx, *tableIdentity, error) {
	if transaction == nil || transaction.tx == nil {
		return nil, nil, ErrTransactionRequired
	}
	_, table, err := requireVerifiedViewStore(transaction.store)
	if err != nil {
		return nil, nil, err
	}
	return transaction.tx, table, nil
}
