package auth

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

	"gorm.io/gorm"
)

type UserSession struct {
	UserID          string    `json:"user_id"`
	MicrosoftNameID string    `json:"microsoft_name_id"`
	Email           string    `json:"email"`
	Name            string    `json:"name"`
	LoggedInAt      time.Time `json:"logged_in_at"`
	ExpiresAt       time.Time `json:"expires_at"`
}

type SessionRecord struct {
	Token           string    `gorm:"type:varchar(128);primaryKey;column:token"`
	UserID          string    `gorm:"type:varchar(191);index;not null;column:user_id"`
	MicrosoftNameID string    `gorm:"type:varchar(255);index;not null;column:microsoft_name_id"`
	Email           string    `gorm:"type:varchar(191);index;not null;column:email"`
	Name            string    `gorm:"type:varchar(191);not null;column:name"`
	LoggedInAt      time.Time `gorm:"not null;column:logged_in_at"`
	ExpiresAt       time.Time `gorm:"index;not null;column:expires_at"`
	CreatedAt       time.Time `gorm:"column:created_at"`
	UpdatedAt       time.Time `gorm:"column:updated_at"`
}

func (SessionRecord) TableName() string { return "saml_sessions" }

var sessionDB *gorm.DB

func InitSessionStoreDB(db *gorm.DB) error {
	if db == nil {
		return fmt.Errorf("database is required")
	}
	if err := db.AutoMigrate(&SessionRecord{}); err != nil {
		return fmt.Errorf("auto migrate saml_sessions: %w", err)
	}
	sessionDB = db
	return nil
}

func CreateSession(session UserSession) (string, error) {
	if sessionDB == nil {
		return "", fmt.Errorf("session store is not initialized")
	}
	token, err := generateToken()
	if err != nil {
		return "", err
	}
	if session.LoggedInAt.IsZero() {
		session.LoggedInAt = time.Now().UTC()
	}
	if session.ExpiresAt.IsZero() {
		session.ExpiresAt = session.LoggedInAt.Add(8 * time.Hour)
	}

	record := SessionRecord{
		Token:           token,
		UserID:          session.UserID,
		MicrosoftNameID: session.MicrosoftNameID,
		Email:           session.Email,
		Name:            session.Name,
		LoggedInAt:      session.LoggedInAt,
		ExpiresAt:       session.ExpiresAt,
	}
	if err := sessionDB.Create(&record).Error; err != nil {
		return "", fmt.Errorf("create session: %w", err)
	}
	return token, nil
}

func GetSession(token string) (*UserSession, error) {
	if sessionDB == nil {
		return nil, fmt.Errorf("session store is not initialized")
	}
	var record SessionRecord
	err := sessionDB.Where("token = ?", token).First(&record).Error
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	if !record.ExpiresAt.After(now) {
		_ = sessionDB.Delete(&SessionRecord{}, "token = ?", token).Error
		return nil, gorm.ErrRecordNotFound
	}
	return &UserSession{
		UserID:          record.UserID,
		MicrosoftNameID: record.MicrosoftNameID,
		Email:           record.Email,
		Name:            record.Name,
		LoggedInAt:      record.LoggedInAt,
		ExpiresAt:       record.ExpiresAt,
	}, nil
}

func DeleteSession(token string) error {
	if sessionDB == nil {
		return fmt.Errorf("session store is not initialized")
	}
	return sessionDB.Delete(&SessionRecord{}, "token = ?", token).Error
}

func DeleteSessionByNameID(nameID string) error {
	if sessionDB == nil {
		return fmt.Errorf("session store is not initialized")
	}
	return sessionDB.Delete(&SessionRecord{}, "microsoft_name_id = ?", nameID).Error
}

func generateToken() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", fmt.Errorf("generate token: %w", err)
	}
	token := hex.EncodeToString(b)
	if token == "" {
		return "", errors.New("empty token")
	}
	return token, nil
}
