package types

import (
	"context"
	"database/sql/driver"
	"fmt"

	"github.com/oklog/ulid/v2"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// ULID wraps ulid.ULID to make it work nicely with GORM
type ULID struct {
	ulid.ULID
}

// NewULIDWrapper creates a new ULID with a new ULID
func NewULIDWrapper() ULID {
	return ULID{ULID: ulid.Make()}
}

// FromULID creates a ULID from a ulid.ULID
func FromULID(id ulid.ULID) ULID {
	return ULID{ULID: id}
}

// ULIDWrapperFromString creates a ULID from a string
func ULIDFromString(id string) (ULID, error) {
	parsed, err := ulid.Parse(id)
	if err != nil {
		return ULID{}, fmt.Errorf("failed to parse ULID string: %w", err)
	}
	return ULID{ULID: parsed}, nil
}

// Scan implements the sql.Scanner interface for ULID
func (u *ULID) Scan(src any) error {
	switch v := src.(type) {
	case []byte:
		// If it's exactly 16 bytes, it's the binary representation
		if len(v) == 16 {
			copy(u.ULID[:], v)
			return nil
		}
		// Otherwise, try as string
		return fmt.Errorf("cannot scan []byte of length %d into ULID", len(v))
	case string:
		parsed, err := ulid.Parse(v)
		if err != nil {
			return fmt.Errorf("failed to parse ULID: %w", err)
		}
		u.ULID = parsed
		return nil
	default:
		return fmt.Errorf("cannot scan %T into ULID", src)
	}
}

// Value implements the driver.Valuer interface for ULID
// Returns the binary representation of the ULID for maximum efficiency
func (u ULID) Value() (driver.Value, error) {
	return u.ULID.Bytes(), nil
}

// GormValue implements the gorm.Valuer interface for ULID
func (u ULID) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
	return clause.Expr{
		SQL:  "?",
		Vars: []any{u.Bytes()},
	}
}

// Compare implements comparison for ULID
func (u ULID) Compare(other ULID) int {
	return u.ULID.Compare(other.ULID)
}
