package models

import (
	"GoMembership/internal/config"
	"GoMembership/internal/constants"
	"GoMembership/internal/utils"
	"GoMembership/pkg/errors"
	"GoMembership/pkg/logger"
	"fmt"
	"slices"
	"time"

	"github.com/alexedwards/argon2id"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type User struct {
	ID             uint `gorm:"primarykey" json:"id"`
	CreatedAt      time.Time
	UpdatedAt      time.Time
	DeletedAt      *time.Time  `gorm:"index"`
	DateOfBirth    time.Time   `gorm:"not null" json:"dateofbirth" binding:"required_unless=RoleID 0,safe_content"`
	Company        string      `json:"company" binding:"omitempty,omitnil,safe_content"`
	Phone          string      `json:"phone" binding:"omitempty,omitnil,safe_content"`
	Notes          string      `json:"notes" binding:"safe_content"`
	FirstName      string      `gorm:"not null" json:"first_name" binding:"required,safe_content"`
	Password       string      `json:"password" binding:"safe_content"`
	Email          string      `gorm:"unique;not null" json:"email" binding:"required,email,safe_content"`
	LastName       string      `gorm:"not null" json:"last_name" binding:"required,safe_content"`
	ProfilePicture string      `json:"profile_picture" binding:"omitempty,omitnil,image,safe_content"`
	Address        string      `gorm:"not null" json:"address" binding:"required,safe_content"`
	ZipCode        string      `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"`
	City           string      `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"`
	Consents       []Consent   `gorm:"constraint:OnUpdate:CASCADE"`
	BankAccount    BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
	BankAccountID  uint
	Verifications  *[]Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
	Membership     Membership      `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
	MembershipID   uint
	Licence        *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"licence"`
	LicenceID      uint
	PaymentStatus  int8 `json:"payment_status"`
	Status         int8 `json:"status"`
	RoleID         int8 `json:"role_id"`
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
	if u.BankAccount.ID != 0 && u.BankAccount.MandateReference == "" {
		mandateReference := u.GenerateMandateReference()

		return tx.Model(&u.BankAccount).Update("MandateReference", mandateReference).Error
	}
	return nil
}

func (u *User) GenerateMandateReference() string {
	return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN)
}

func (u *User) SetPassword(plaintextPassword string) error {
	if plaintextPassword == "" {
		return nil
	}

	hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
	if err != nil {
		return err
	}
	u.Password = hash
	return nil
}

func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
	return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
}

func (u *User) PasswordExists() bool {
	return u.Password != ""
}

func (u *User) Delete(db *gorm.DB) error {
	return db.Delete(&User{}, "id = ?", u.ID).Error
}

func (u *User) Create(db *gorm.DB) error {
	return db.Transaction(func(tx *gorm.DB) error {
		// Create the base User record (omit associations to handle them separately)
		if err := tx.Create(u).Error; err != nil {
			return err
		}

		// Replace associated Categories (assumes Categories already exist)
		if u.Licence != nil && len(u.Licence.Categories) > 0 {
			if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
				return err
			}
		}
		logger.Info.Printf("user created: %#v", u.Safe())
		// Preload all associations to return the fully populated User
		return tx.
			Preload("Membership").
			Preload("Membership.SubscriptionModel").
			Preload("Licence").
			Preload("Licence.Categories").
			First(u, u.ID).Error // Refresh the user object with all associations
	})
}

func (u *User) Update(db *gorm.DB) error {

	err := db.Transaction(func(tx *gorm.DB) error {
		// Check if the user exists in the database
		var existingUser User

		logger.Info.Printf("updating user: %#v", u)
		if err := tx.
			Preload("Membership").
			Preload("Membership.SubscriptionModel").
			Preload("Licence").
			Preload("Licence.Categories").
			Preload("Verifications").
			First(&existingUser, u.ID).Error; err != nil {
			return err
		}
		// Update the user's main fields
		result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Membership", "Licence", "Verifications").Updates(u)
		if result.Error != nil {
			logger.Error.Printf("User update error in update user: %#v", result.Error)
			return result.Error
		}
		if result.RowsAffected == 0 {
			return errors.ErrNoRowsAffected
		}

		if u.Password != "" {
			if err := tx.Model(&existingUser).
				Update("Password", u.Password).Error; err != nil {
				logger.Error.Printf("Password update error in update user: %#v", err)
				return err
			}
		}

		// Update the Membership if provided
		if u.Membership.ID != 0 {
			if err := tx.Model(&existingUser.Membership).Updates(u.Membership).Error; err != nil {
				logger.Error.Printf("Membership update error in update user: %#v", err)
				return err
			}
		}
		if u.Licence != nil {
			u.Licence.UserID = existingUser.ID

			if err := tx.Save(u.Licence).Error; err != nil {
				return err
			}

			if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
				return err
			}

			if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
				return err
			}
		}
		// if u.Licence != nil {
		// 	if existingUser.Licence == nil || existingUser.LicenceID == 0 {
		// 		u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User
		// 		if err := tx.Create(u.Licence).Error; err != nil {
		// 			return err
		// 		}
		// 		existingUser.Licence = u.Licence
		// 		existingUser.LicenceID = u.Licence.ID
		// 		if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
		// 			return err
		// 		}
		// 	}
		// 	if err := tx.Model(existingUser.Licence).Updates(u.Licence).Error; err != nil {
		// 		return err
		// 	}
		// 	// Update Categories association
		// 	if err := tx.Model(existingUser.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
		// 		return err
		// 	}
		// }

		if u.Verifications != nil {
			if err := tx.Save(*u.Verifications).Error; err != nil {
				return err
			}
		}
		return nil
	})

	if err != nil {
		return err
	}

	return db.
		Preload("Membership").
		Preload("Membership.SubscriptionModel").
		Preload("Licence").
		Preload("Licence.Categories").
		Preload("Verifications").
		First(&u, u.ID).Error
}

func (u *User) FromID(db *gorm.DB, userID *uint) error {
	var user User
	result := db.
		Preload(clause.Associations).
		Preload("Membership").
		Preload("Membership.SubscriptionModel").
		Preload("Licence").
		Preload("Licence.Categories").
		Preload("Verifications").
		First(&user, userID)
	if result.Error != nil {
		if result.Error == gorm.ErrRecordNotFound {
			return gorm.ErrRecordNotFound
		}
		return result.Error
	}
	*u = user
	return nil
}

func (u *User) FromEmail(db *gorm.DB, email *string) error {
	var user User
	result := db.
		Preload(clause.Associations).
		Preload("Membership").
		Preload("Membership.SubscriptionModel").
		Preload("Licence").
		Preload("Licence.Categories").
		Preload("Verifications").
		Where("email = ?", email).First(&user)
	if result.Error != nil {
		if result.Error == gorm.ErrRecordNotFound {
			return gorm.ErrRecordNotFound
		}
		return result.Error
	}
	*u = user
	return nil
}

func (u *User) FromContext(db *gorm.DB, c *gin.Context) error {
	tokenString, err := c.Cookie("jwt")
	if err != nil {
		return err
	}
	jwtUserID, err := extractUserIDFrom(tokenString)
	if err != nil {
		return err
	}
	if err = u.FromID(db, &jwtUserID); err != nil {
		return err
	}
	return nil
}

func (u *User) IsVerified() bool {
	return u.Status > constants.DisabledStatus
}

func (u *User) HasPrivilege(privilege int8) bool {
	return u.RoleID >= privilege
}

func (u *User) IsAdmin() bool {
	return u.RoleID == constants.Roles.Admin
}

func (u *User) IsMember() bool {
	return u.RoleID == constants.Roles.Member
}

func (u *User) IsSupporter() bool {
	return u.RoleID == constants.Roles.Supporter
}

func (u *User) SetVerification(verificationType string) (*Verification, error) {
	if u.Verifications == nil {
		u.Verifications = new([]Verification)
	}
	token, err := utils.GenerateVerificationToken()
	if err != nil {
		return nil, err
	}
	v := Verification{
		UserID:            u.ID,
		VerificationToken: token,
		Type:              verificationType,
	}
	if vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == v.Type }); vi > -1 {
		(*u.Verifications)[vi] = v
	} else {
		*u.Verifications = append(*u.Verifications, v)
	}
	return &v, nil
}

func (u *User) GetVerification(verificationType string) (*Verification, error) {
	if u.Verifications == nil {
		return nil, errors.ErrNoData
	}
	vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == verificationType })
	if vi == -1 {
		return nil, errors.ErrNotFound
	}
	return &(*u.Verifications)[vi], nil
}

func (u *User) Verify(token string, verificationType string) bool {
	if token == "" || verificationType == "" {
		logger.Error.Printf("token or verification type are empty in user.Verify")
		return false
	}

	vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool {
		return vsl.Type == verificationType && vsl.VerificationToken == token
	})

	if vi == -1 {
		logger.Error.Printf("Couldn't find verification in users verifications")
		return false
	}

	if (*u.Verifications)[vi].VerifiedAt != nil {
		logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", (*u.Verifications)[vi])
		return false
	}

	t := time.Now()
	(*u.Verifications)[vi].VerifiedAt = &t
	return true
}

func (u *User) Safe() map[string]interface{} {
	result := map[string]interface{}{
		"email":       u.Email,
		"first_name":  u.FirstName,
		"last_name":   u.LastName,
		"phone":       u.Phone,
		"notes":       u.Notes,
		"address":     u.Address,
		"zip_code":    u.ZipCode,
		"city":        u.City,
		"status":      u.Status,
		"id":          u.ID,
		"role_id":     u.RoleID,
		"company":     u.Company,
		"dateofbirth": u.DateOfBirth,
		"membership": map[string]interface{}{
			"id":         u.Membership.ID,
			"start_date": u.Membership.StartDate,
			"end_date":   u.Membership.EndDate,
			"status":     u.Membership.Status,
			"subscription_model": map[string]interface{}{
				"id":                 u.Membership.SubscriptionModel.ID,
				"name":               u.Membership.SubscriptionModel.Name,
				"details":            u.Membership.SubscriptionModel.Details,
				"conditions":         u.Membership.SubscriptionModel.Conditions,
				"monthly_fee":        u.Membership.SubscriptionModel.MonthlyFee,
				"hourly_rate":        u.Membership.SubscriptionModel.HourlyRate,
				"included_per_year":  u.Membership.SubscriptionModel.IncludedPerYear,
				"included_per_month": u.Membership.SubscriptionModel.IncludedPerMonth,
			},
		},
		"licence": map[string]interface{}{
			"id": 0,
		},
		"bank_account": map[string]interface{}{
			"id":                  u.BankAccount.ID,
			"mandate_date_signed": u.BankAccount.MandateDateSigned,
			"bank":                u.BankAccount.Bank,
			"account_holder_name": u.BankAccount.AccountHolderName,
			"iban":                u.BankAccount.IBAN,
			"bic":                 u.BankAccount.BIC,
			"mandate_reference":   u.BankAccount.MandateReference,
		},
	}

	if u.Licence != nil {
		result["licence"] = map[string]interface{}{
			"id":              u.Licence.ID,
			"number":          u.Licence.Number,
			"status":          u.Licence.Status,
			"issued_date":     u.Licence.IssuedDate,
			"expiration_date": u.Licence.ExpirationDate,
			"country":         u.Licence.IssuingCountry,
			"categories":      u.Licence.Categories,
		}
	}

	return result
}

func extractUserIDFrom(tokenString string) (uint, error) {
	jwtSigningMethod := jwt.SigningMethodHS256
	jwtParser := jwt.NewParser(jwt.WithValidMethods([]string{jwtSigningMethod.Alg()}))
	token, err := jwtParser.Parse(tokenString, func(_ *jwt.Token) (interface{}, error) {
		return []byte(config.Auth.JWTSecret), nil
	})

	// Handle parsing errors (excluding expiration error)
	if err != nil && !errors.Is(err, jwt.ErrTokenExpired) || token == nil {
		logger.Error.Printf("Error parsing token: %v", err)
		return 0, err
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		logger.Error.Print("Invalid token claims structure")
		return 0, fmt.Errorf("invalid token claims format")
	}

	// Validate required session_id claim
	if _, exists := claims["session_id"]; !exists {
		logger.Error.Print("Missing session_id in token claims")
		return 0, fmt.Errorf("missing session_id claim")
	}

	// Return token, claims, and original error (might be expiration)

	if _, exists := claims["session_id"]; !exists {
		logger.Error.Print("Missing session_id in token claims")
		return 0, fmt.Errorf("missing session_id claim")
	}
	id, ok := claims["user_id"]
	if !ok {
		return 0, fmt.Errorf("missing user_id claim")
	}

	return uint(id.(float64)), nil
}

func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
	var users []User
	result := db.
		Preload(clause.Associations).
		Preload("Membership").
		Preload("Membership.SubscriptionModel").
		Preload("Licence").
		Preload("Licence.Categories").
		Preload("Verifications").
		Where(where).Find(&users)
	if result.Error != nil {
		if result.Error == gorm.ErrRecordNotFound {
			return nil, gorm.ErrRecordNotFound
		}
		return nil, result.Error
	}
	return &users, nil
}
