package repository

import (
	"errors"
	"strings"
	"time"

	"github.com/openaccounting/oa-server/core/model/types"
	"github.com/openaccounting/oa-server/core/util"
	"github.com/openaccounting/oa-server/models"
	"gorm.io/gorm"
)

// GormRepository implements the same interfaces as core/model/db but uses GORM
type GormRepository struct {
	db *gorm.DB
}

// Note: GormRepository implements most of the Datastore interface
// Some methods like DeleteAndInsertTransaction need to be added for full compatibility

// NewGormRepository creates a new GORM repository
func NewGormRepository(db *gorm.DB) *GormRepository {
	return &GormRepository{db: db}
}

// UserInterface implementation
func (r *GormRepository) InsertUser(user *types.User) error {
	user.Inserted = time.Now()
	user.Updated = user.Inserted
	user.PasswordReset = ""

	// Convert types.User to models.User
	gormUser := &models.User{
		ID:              []byte(user.Id), // Convert string ID to []byte
		Inserted:        uint64(util.TimeToMs(user.Inserted)),
		Updated:         uint64(util.TimeToMs(user.Updated)),
		FirstName:       user.FirstName,
		LastName:        user.LastName,
		Email:           user.Email,
		PasswordHash:    user.PasswordHash,
		AgreeToTerms:    user.AgreeToTerms,
		PasswordReset:   user.PasswordReset,
		EmailVerified:   user.EmailVerified,
		EmailVerifyCode: user.EmailVerifyCode,
		SignupSource:    user.SignupSource,
	}

	result := r.db.Create(gormUser)
	if result.Error != nil {
		return result.Error
	}

	if result.RowsAffected < 1 {
		return errors.New("unable to insert user into db")
	}

	return nil
}

func (r *GormRepository) VerifyUser(code string) error {
	result := r.db.Model(&models.User{}).
		Where("email_verify_code = ?", code).
		Updates(map[string]interface{}{
			"updated":        util.TimeToMs(time.Now()),
			"email_verified": true,
		})

	if result.Error != nil {
		return result.Error
	}

	if result.RowsAffected == 0 {
		return errors.New("invalid code")
	}

	return nil
}

func (r *GormRepository) UpdateUser(user *types.User) error {
	user.Updated = time.Now()

	result := r.db.Model(&models.User{}).
		Where("id = ?", []byte(user.Id)).
		Updates(map[string]interface{}{
			"updated":        util.TimeToMs(user.Updated),
			"password_hash":  user.PasswordHash,
			"password_reset": "",
		})

	return result.Error
}

func (r *GormRepository) UpdateUserResetPassword(user *types.User) error {
	user.Updated = time.Now()

	result := r.db.Model(&models.User{}).
		Where("id = ?", []byte(user.Id)).
		Updates(map[string]interface{}{
			"updated":        util.TimeToMs(user.Updated),
			"password_reset": user.PasswordReset,
		})

	return result.Error
}

func (r *GormRepository) GetVerifiedUserByEmail(email string) (*types.User, error) {
	var gormUser models.User
	result := r.db.Where("email = ? AND email_verified = ?", 
		strings.TrimSpace(strings.ToLower(email)), true).
		First(&gormUser)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormUserToTypesUser(&gormUser), nil
}

func (r *GormRepository) GetUserByActiveSession(sessionId string) (*types.User, error) {
	var gormUser models.User
	result := r.db.Table("users").
		Select("users.*").
		Joins("JOIN sessions ON sessions.user_id = users.id").
		Where("sessions.terminated IS NULL AND sessions.id = ?", []byte(sessionId)).
		First(&gormUser)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormUserToTypesUser(&gormUser), nil
}

func (r *GormRepository) GetUserByApiKey(keyId string) (*types.User, error) {
	var gormUser models.User
	result := r.db.Table("users").
		Select("users.*").
		Joins("JOIN api_keys ON api_keys.user_id = users.id").
		Where("api_keys.deleted_at IS NULL AND api_keys.id = ?", []byte(keyId)).
		First(&gormUser)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormUserToTypesUser(&gormUser), nil
}

func (r *GormRepository) GetUserByResetCode(code string) (*types.User, error) {
	var gormUser models.User
	result := r.db.Where("password_reset = ?", code).First(&gormUser)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormUserToTypesUser(&gormUser), nil
}

func (r *GormRepository) GetUserByEmailVerifyCode(code string) (*types.User, error) {
	// only allow this for 3 days
	minInserted := (time.Now().UnixNano() / 1000000) - (3 * 24 * 60 * 60 * 1000)
	
	var gormUser models.User
	result := r.db.Where("email_verify_code = ? AND inserted > ?", code, minInserted).
		First(&gormUser)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormUserToTypesUser(&gormUser), nil
}

func (r *GormRepository) GetOrgAdmins(orgId string) ([]*types.User, error) {
	var gormUsers []models.User
	result := r.db.Table("users").
		Select("users.*").
		Joins("JOIN user_orgs ON user_orgs.user_id = users.id").
		Where("user_orgs.admin = ? AND user_orgs.org_id = ?", true, []byte(orgId)).
		Find(&gormUsers)

	if result.Error != nil {
		return nil, result.Error
	}

	users := make([]*types.User, len(gormUsers))
	for i, gormUser := range gormUsers {
		users[i] = r.convertGormUserToTypesUser(&gormUser)
	}

	return users, nil
}

// Helper function to convert GORM User to types.User
func (r *GormRepository) convertGormUserToTypesUser(gormUser *models.User) *types.User {
	return &types.User{
		Id:              string(gormUser.ID),
		Inserted:        util.MsToTime(int64(gormUser.Inserted)),
		Updated:         util.MsToTime(int64(gormUser.Updated)),
		FirstName:       gormUser.FirstName,
		LastName:        gormUser.LastName,
		Email:           gormUser.Email,
		PasswordHash:    gormUser.PasswordHash,
		AgreeToTerms:    gormUser.AgreeToTerms,
		PasswordReset:   gormUser.PasswordReset,
		EmailVerified:   gormUser.EmailVerified,
		EmailVerifyCode: gormUser.EmailVerifyCode,
		SignupSource:    gormUser.SignupSource,
	}
}

// AccountInterface implementation
func (r *GormRepository) InsertAccount(account *types.Account) error {
	account.Inserted = time.Now()
	account.Updated = account.Inserted

	// Convert types.Account to models.Account
	gormAccount := &models.Account{
		ID:           []byte(account.Id),
		OrgID:        []byte(account.OrgId),
		Inserted:     uint64(util.TimeToMs(account.Inserted)),
		Updated:      uint64(util.TimeToMs(account.Updated)),
		Name:         account.Name,
		Parent:       []byte(account.Parent),
		Currency:     account.Currency,
		Precision:    account.Precision,
		DebitBalance: account.DebitBalance,
	}

	return r.db.Create(gormAccount).Error
}

func (r *GormRepository) UpdateAccount(account *types.Account) error {
	account.Updated = time.Now()

	result := r.db.Model(&models.Account{}).
		Where("id = ?", []byte(account.Id)).
		Updates(map[string]interface{}{
			"updated":       util.TimeToMs(account.Updated),
			"name":          account.Name,
			"parent":        []byte(account.Parent),
			"currency":      account.Currency,
			"precision":     account.Precision,
			"debit_balance": account.DebitBalance,
		})

	return result.Error
}

func (r *GormRepository) GetAccount(id string) (*types.Account, error) {
	var gormAccount models.Account
	result := r.db.Where("id = ?", []byte(id)).First(&gormAccount)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormAccountToTypesAccount(&gormAccount), nil
}

func (r *GormRepository) GetAccountsByOrgId(orgId string) ([]*types.Account, error) {
	var gormAccounts []models.Account
	result := r.db.Where("org_id = ?", []byte(orgId)).Find(&gormAccounts)

	if result.Error != nil {
		return nil, result.Error
	}

	accounts := make([]*types.Account, len(gormAccounts))
	for i, gormAccount := range gormAccounts {
		accounts[i] = r.convertGormAccountToTypesAccount(&gormAccount)
	}

	return accounts, nil
}

func (r *GormRepository) GetPermissionedAccountIds(orgId, userId, tokenId string) ([]string, error) {
	var accountIds []string
	result := r.db.Table("permissions").
		Select("DISTINCT LOWER(HEX(account_id)) as account_id").
		Where("org_id = ? AND user_id = ?", []byte(orgId), []byte(userId)).
		Pluck("account_id", &accountIds)

	return accountIds, result.Error
}

func (r *GormRepository) GetSplitCountByAccountId(id string) (int64, error) {
	var count int64
	result := r.db.Model(&models.Split{}).
		Where("account_id = ?", []byte(id)).
		Count(&count)

	return count, result.Error
}

func (r *GormRepository) GetChildCountByAccountId(id string) (int64, error) {
	var count int64
	result := r.db.Model(&models.Account{}).
		Where("parent = ?", []byte(id)).
		Count(&count)

	return count, result.Error
}

func (r *GormRepository) DeleteAccount(id string) error {
	return r.db.Where("id = ?", []byte(id)).Delete(&models.Account{}).Error
}

func (r *GormRepository) GetRootAccount(orgId string) (*types.Account, error) {
	var gormAccount models.Account
	result := r.db.Where("org_id = ? AND parent = ?", []byte(orgId), []byte{0}).
		First(&gormAccount)

	if result.Error != nil {
		return nil, result.Error
	}

	return r.convertGormAccountToTypesAccount(&gormAccount), nil
}

// Balance-related methods (simplified implementations)
func (r *GormRepository) AddBalances(accounts []*types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

func (r *GormRepository) AddNativeBalancesCost(accounts []*types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

func (r *GormRepository) AddNativeBalancesNearestInTime(accounts []*types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

func (r *GormRepository) AddBalance(account *types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

func (r *GormRepository) AddNativeBalanceCost(account *types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

func (r *GormRepository) AddNativeBalanceNearestInTime(account *types.Account, date time.Time) error {
	// Implementation would need to be completed based on your balance calculation logic
	return nil
}

// Helper function to convert GORM Account to types.Account
func (r *GormRepository) convertGormAccountToTypesAccount(gormAccount *models.Account) *types.Account {
	return &types.Account{
		Id:           string(gormAccount.ID),
		OrgId:        string(gormAccount.OrgID),
		Inserted:     util.MsToTime(int64(gormAccount.Inserted)),
		Updated:      util.MsToTime(int64(gormAccount.Updated)),
		Name:         gormAccount.Name,
		Parent:       string(gormAccount.Parent),
		Currency:     gormAccount.Currency,
		Precision:    gormAccount.Precision,
		DebitBalance: gormAccount.DebitBalance,
		// Balance fields would be populated by the AddBalance methods
	}
}

// Escape method for SQL injection protection (GORM handles this automatically)
func (r *GormRepository) Escape(sql string) string {
	// GORM handles SQL injection protection automatically
	// This method is kept for interface compatibility
	return sql
}