package models

import (
	"crypto/rand"
	"encoding/hex"
	"time"

	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// User represents the auth_user table (Django's default User model)
type User struct {
	ID          uint      `gorm:"primaryKey" json:"id"`
	Password    string    `gorm:"column:password;size:128;not null" json:"-"`
	LastLogin   *time.Time `gorm:"column:last_login" json:"last_login,omitempty"`
	IsSuperuser bool      `gorm:"column:is_superuser;default:false" json:"is_superuser"`
	Username    string    `gorm:"column:username;uniqueIndex;size:150;not null" json:"username"`
	FirstName   string    `gorm:"column:first_name;size:150" json:"first_name"`
	LastName    string    `gorm:"column:last_name;size:150" json:"last_name"`
	Email       string    `gorm:"column:email;uniqueIndex;size:254" json:"email"`
	IsStaff     bool      `gorm:"column:is_staff;default:false" json:"is_staff"`
	IsActive    bool      `gorm:"column:is_active;default:true" json:"is_active"`
	DateJoined  time.Time `gorm:"column:date_joined;autoCreateTime" json:"date_joined"`

	// Relations (not stored in auth_user table)
	Profile          *UserProfile          `gorm:"foreignKey:UserID" json:"profile,omitempty"`
	AuthToken        *AuthToken            `gorm:"foreignKey:UserID" json:"-"`
	OwnedResidences  []Residence           `gorm:"foreignKey:OwnerID" json:"-"`
	SharedResidences []Residence           `gorm:"many2many:residence_residence_users;" json:"-"`
	NotificationPref *NotificationPreference `gorm:"foreignKey:UserID" json:"-"`
	Subscription     *UserSubscription     `gorm:"foreignKey:UserID" json:"-"`
}

// TableName returns the table name for GORM
func (User) TableName() string {
	return "auth_user"
}

// SetPassword hashes and sets the password
func (u *User) SetPassword(password string) error {
	// Django uses PBKDF2_SHA256 by default, but we'll use bcrypt for Go
	// Note: This means passwords set by Django won't work with Go's check
	// For migration, you'd need to either:
	// 1. Force password reset for all users
	// 2. Implement Django's PBKDF2 hasher in Go
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	u.Password = string(hash)
	return nil
}

// CheckPassword verifies a password against the stored hash
func (u *User) CheckPassword(password string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
	return err == nil
}

// GetFullName returns the user's full name
func (u *User) GetFullName() string {
	if u.FirstName != "" && u.LastName != "" {
		return u.FirstName + " " + u.LastName
	}
	if u.FirstName != "" {
		return u.FirstName
	}
	return u.Username
}

// AuthToken represents the user_authtoken table
type AuthToken struct {
	Key       string    `gorm:"column:key;primaryKey;size:40" json:"key"`
	UserID    uint      `gorm:"column:user_id;uniqueIndex;not null" json:"user_id"`
	Created   time.Time `gorm:"column:created;autoCreateTime" json:"created"`

	// Relations
	User User `gorm:"foreignKey:UserID" json:"-"`
}

// TableName returns the table name for GORM
func (AuthToken) TableName() string {
	return "user_authtoken"
}

// BeforeCreate generates a token key if not provided
func (t *AuthToken) BeforeCreate(tx *gorm.DB) error {
	if t.Key == "" {
		t.Key = generateToken()
	}
	if t.Created.IsZero() {
		t.Created = time.Now().UTC()
	}
	return nil
}

// generateToken creates a random 40-character hex token
func generateToken() string {
	b := make([]byte, 20)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// GetOrCreate gets an existing token or creates a new one for the user
func GetOrCreateToken(tx *gorm.DB, userID uint) (*AuthToken, error) {
	var token AuthToken
	result := tx.Where("user_id = ?", userID).First(&token)

	if result.Error == gorm.ErrRecordNotFound {
		token = AuthToken{UserID: userID}
		if err := tx.Create(&token).Error; err != nil {
			return nil, err
		}
	} else if result.Error != nil {
		return nil, result.Error
	}

	return &token, nil
}

// UserProfile represents the user_userprofile table
type UserProfile struct {
	BaseModel
	UserID         uint       `gorm:"column:user_id;uniqueIndex;not null" json:"user_id"`
	Verified       bool       `gorm:"column:verified;default:false" json:"verified"`
	Bio            string     `gorm:"column:bio;type:text" json:"bio"`
	PhoneNumber    string     `gorm:"column:phone_number;size:15" json:"phone_number"`
	DateOfBirth    *time.Time `gorm:"column:date_of_birth;type:date" json:"date_of_birth,omitempty"`
	ProfilePicture string     `gorm:"column:profile_picture;size:100" json:"profile_picture"`

	// Relations
	User User `gorm:"foreignKey:UserID" json:"-"`
}

// TableName returns the table name for GORM
func (UserProfile) TableName() string {
	return "user_userprofile"
}

// ConfirmationCode represents the user_confirmationcode table
type ConfirmationCode struct {
	BaseModel
	UserID    uint      `gorm:"column:user_id;index;not null" json:"user_id"`
	Code      string    `gorm:"column:code;size:6;not null" json:"-"`
	ExpiresAt time.Time `gorm:"column:expires_at;not null" json:"expires_at"`
	IsUsed    bool      `gorm:"column:is_used;default:false" json:"is_used"`

	// Relations
	User User `gorm:"foreignKey:UserID" json:"-"`
}

// TableName returns the table name for GORM
func (ConfirmationCode) TableName() string {
	return "user_confirmationcode"
}

// IsValid checks if the confirmation code is still valid
func (c *ConfirmationCode) IsValid() bool {
	return !c.IsUsed && time.Now().UTC().Before(c.ExpiresAt)
}

// GenerateCode creates a random 6-digit code
func GenerateConfirmationCode() string {
	b := make([]byte, 3)
	rand.Read(b)
	// Convert to 6-digit number
	num := int(b[0])<<16 | int(b[1])<<8 | int(b[2])
	return string(rune('0'+num%10)) + string(rune('0'+(num/10)%10)) +
		string(rune('0'+(num/100)%10)) + string(rune('0'+(num/1000)%10)) +
		string(rune('0'+(num/10000)%10)) + string(rune('0'+(num/100000)%10))
}

// PasswordResetCode represents the user_passwordresetcode table
type PasswordResetCode struct {
	BaseModel
	UserID      uint      `gorm:"column:user_id;index;not null" json:"user_id"`
	CodeHash    string    `gorm:"column:code_hash;size:128;not null" json:"-"`
	ResetToken  string    `gorm:"column:reset_token;uniqueIndex;size:64;not null" json:"reset_token"`
	ExpiresAt   time.Time `gorm:"column:expires_at;not null" json:"expires_at"`
	Used        bool      `gorm:"column:used;default:false" json:"used"`
	Attempts    int       `gorm:"column:attempts;default:0" json:"attempts"`
	MaxAttempts int       `gorm:"column:max_attempts;default:5" json:"max_attempts"`

	// Relations
	User User `gorm:"foreignKey:UserID" json:"-"`
}

// TableName returns the table name for GORM
func (PasswordResetCode) TableName() string {
	return "user_passwordresetcode"
}

// SetCode hashes and stores the reset code
func (p *PasswordResetCode) SetCode(code string) error {
	hash, err := bcrypt.GenerateFromPassword([]byte(code), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	p.CodeHash = string(hash)
	return nil
}

// CheckCode verifies a code against the stored hash
func (p *PasswordResetCode) CheckCode(code string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(p.CodeHash), []byte(code))
	return err == nil
}

// IsValid checks if the reset code is still valid
func (p *PasswordResetCode) IsValid() bool {
	return !p.Used && time.Now().UTC().Before(p.ExpiresAt) && p.Attempts < p.MaxAttempts
}

// IncrementAttempts increments the attempt counter
func (p *PasswordResetCode) IncrementAttempts(tx *gorm.DB) error {
	p.Attempts++
	return tx.Model(p).Update("attempts", p.Attempts).Error
}

// MarkAsUsed marks the code as used
func (p *PasswordResetCode) MarkAsUsed(tx *gorm.DB) error {
	p.Used = true
	return tx.Model(p).Update("used", true).Error
}

// GenerateResetToken creates a URL-safe token
func GenerateResetToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// AppleSocialAuth represents a user's linked Apple ID for Sign in with Apple
type AppleSocialAuth struct {
	ID             uint      `gorm:"primaryKey" json:"id"`
	UserID         uint      `gorm:"uniqueIndex;not null" json:"user_id"`
	User           User      `gorm:"foreignKey:UserID" json:"-"`
	AppleID        string    `gorm:"column:apple_id;size:255;uniqueIndex;not null" json:"apple_id"` // Apple's unique subject ID
	Email          string    `gorm:"column:email;size:254" json:"email"`                            // May be private relay
	IsPrivateEmail bool      `gorm:"column:is_private_email;default:false" json:"is_private_email"`
	CreatedAt      time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
	UpdatedAt      time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}

// TableName returns the table name for GORM
func (AppleSocialAuth) TableName() string {
	return "user_applesocialauth"
}

// GoogleSocialAuth represents a user's linked Google account for Sign in with Google
type GoogleSocialAuth struct {
	ID        uint      `gorm:"primaryKey" json:"id"`
	UserID    uint      `gorm:"uniqueIndex;not null" json:"user_id"`
	User      User      `gorm:"foreignKey:UserID" json:"-"`
	GoogleID  string    `gorm:"column:google_id;size:255;uniqueIndex;not null" json:"google_id"` // Google's unique subject ID
	Email     string    `gorm:"column:email;size:254" json:"email"`
	Name      string    `gorm:"column:name;size:255" json:"name"`
	Picture   string    `gorm:"column:picture;size:512" json:"picture"` // Profile picture URL
	CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
	UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}

// TableName returns the table name for GORM
func (GoogleSocialAuth) TableName() string {
	return "user_googlesocialauth"
}
