package models

import (
	"crypto/rand"
	"database/sql/driver"
	"fmt"
	"time"

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

// ULID is a custom type that wraps oklog/ulid/v2.ULID and implements
// GORM's Scanner and Valuer interfaces for database operations
type ULID struct {
	ulid.ULID
}

// NewULID creates a new ULID from a ulid.ULID
func NewULID(u ulid.ULID) ULID {
	return ULID{ULID: u}
}

// ParseULID parses a string into a ULID
func ParseULID(s string) (ULID, error) {
	u, err := ulid.Parse(s)
	if err != nil {
		return ULID{}, err
	}
	return ULID{ULID: u}, nil
}

// MustParseULID parses a string into a ULID and panics on error
func MustParseULID(s string) ULID {
	u, err := ParseULID(s)
	if err != nil {
		panic(err)
	}
	return u
}

// GenerateULID creates a new ULID with the current timestamp
func GenerateULID() ULID {
	return ULID{ULID: ulid.MustNew(ulid.Timestamp(time.Now()), rand.Reader)}
}

// GenerateULIDWithTime creates a new ULID with the specified timestamp
func GenerateULIDWithTime(t time.Time) ULID {
	return ULID{ULID: ulid.MustNew(ulid.Timestamp(t), rand.Reader)}
}

// Scan implements the sql.Scanner interface for reading from database
func (u *ULID) Scan(value interface{}) error {
	if value == nil {
		*u = ULID{}
		return nil
	}

	switch v := value.(type) {
	case string:
		parsed, err := ulid.Parse(v)
		if err != nil {
			return fmt.Errorf("cannot parse ULID from string: %w", err)
		}
		u.ULID = parsed
		return nil
	case []byte:
		parsed, err := ulid.Parse(string(v))
		if err != nil {
			return fmt.Errorf("cannot parse ULID from bytes: %w", err)
		}
		u.ULID = parsed
		return nil
	default:
		return fmt.Errorf("cannot scan %T into ULID", value)
	}
}

// Value implements the driver.Valuer interface for writing to database
func (u ULID) Value() (driver.Value, error) {
	if u.ULID == (ulid.ULID{}) {
		return nil, nil
	}
	return u.ULID.String(), nil
}

// GormDataType returns the data type for GORM
func (ULID) GormDataType() string {
	return "char(26)"
}

// GormDBDataType returns the database-specific data type for GORM
func (ULID) GormDBDataType(db *gorm.DB, field *schema.Field) string {
	switch db.Dialector.Name() {
	case "postgres":
		return "char(26)"
	case "mysql":
		return "char(26)"
	case "sqlite":
		return "text"
	default:
		return "char(26)"
	}
}

// MarshalJSON implements json.Marshaler
func (u ULID) MarshalJSON() ([]byte, error) {
	return []byte(`"` + u.ULID.String() + `"`), nil
}

// UnmarshalJSON implements json.Unmarshaler
func (u *ULID) UnmarshalJSON(data []byte) error {
	if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
		return fmt.Errorf("invalid JSON string for ULID")
	}

	str := string(data[1 : len(data)-1])
	if str == "" {
		*u = ULID{}
		return nil
	}

	parsed, err := ulid.Parse(str)
	if err != nil {
		return fmt.Errorf("cannot parse ULID from JSON: %w", err)
	}

	u.ULID = parsed
	return nil
}

// String returns the string representation of the ULID
func (u ULID) String() string {
	return u.ULID.String()
}

// IsZero returns true if the ULID is zero value
func (u ULID) IsZero() bool {
	return u.ULID == ulid.ULID{}
}
