package repository

import (
	"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"
)

// OrgInterface implementation
func (r *GormRepository) CreateOrg(org *types.Org, userId string, accounts []*types.Account) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		org.Inserted = time.Now()
		org.Updated = org.Inserted

		// Create org
		gormOrg := &models.Org{
			ID:        []byte(org.Id),
			Inserted:  uint64(util.TimeToMs(org.Inserted)),
			Updated:   uint64(util.TimeToMs(org.Updated)),
			Name:      org.Name,
			Currency:  org.Currency,
			Precision: org.Precision,
			Timezone:  org.Timezone,
		}

		if err := tx.Create(gormOrg).Error; err != nil {
			return err
		}

		// Create accounts
		for _, account := range accounts {
			gormAccount := &models.Account{
				ID:           []byte(account.Id),
				OrgID:        []byte(account.OrgId),
				Inserted:     uint64(util.TimeToMs(time.Now())),
				Updated:      uint64(util.TimeToMs(time.Now())),
				Name:         account.Name,
				Parent:       []byte(account.Parent),
				Currency:     account.Currency,
				Precision:    account.Precision,
				DebitBalance: account.DebitBalance,
			}
			if err := tx.Create(gormAccount).Error; err != nil {
				return err
			}
		}

		// Create userorg association
		userOrg := &models.UserOrg{
			UserID: []byte(userId),
			OrgID:  []byte(org.Id),
			Admin:  true,
		}

		return tx.Create(userOrg).Error
	})
}

func (r *GormRepository) UpdateOrg(org *types.Org) error {
	org.Updated = time.Now()

	return r.db.Model(&models.Org{}).
		Where("id = ?", []byte(org.Id)).
		Updates(map[string]interface{}{
			"updated":   util.TimeToMs(org.Updated),
			"name":      org.Name,
			"currency":  org.Currency,
			"precision": org.Precision,
			"timezone":  org.Timezone,
		}).Error
}

func (r *GormRepository) GetOrg(orgId, userId string) (*types.Org, error) {
	var gormOrg models.Org
	result := r.db.Table("orgs").
		Select("orgs.*").
		Joins("JOIN user_orgs ON user_orgs.org_id = orgs.id").
		Where("orgs.id = ? AND user_orgs.user_id = ?", []byte(orgId), []byte(userId)).
		First(&gormOrg)

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

	return r.convertGormOrgToTypesOrg(&gormOrg), nil
}

func (r *GormRepository) GetOrgs(userId string) ([]*types.Org, error) {
	var gormOrgs []models.Org
	result := r.db.Table("orgs").
		Select("orgs.*").
		Joins("JOIN user_orgs ON user_orgs.org_id = orgs.id").
		Where("user_orgs.user_id = ?", []byte(userId)).
		Find(&gormOrgs)

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

	orgs := make([]*types.Org, len(gormOrgs))
	for i, gormOrg := range gormOrgs {
		orgs[i] = r.convertGormOrgToTypesOrg(&gormOrg)
	}

	return orgs, nil
}

func (r *GormRepository) GetOrgUserIds(orgId string) ([]string, error) {
	var userIds []string
	result := r.db.Table("user_orgs").
		Select("LOWER(HEX(user_id)) as user_id").
		Where("org_id = ?", []byte(orgId)).
		Pluck("user_id", &userIds)

	return userIds, result.Error
}

func (r *GormRepository) InsertInvite(invite *types.Invite) error {
	invite.Inserted = time.Now()
	invite.Updated = invite.Inserted

	gormInvite := &models.Invite{
		ID:       invite.Id,
		OrgID:    []byte(invite.OrgId),
		Inserted: uint64(util.TimeToMs(invite.Inserted)),
		Updated:  uint64(util.TimeToMs(invite.Updated)),
		Email:    invite.Email,
		Accepted: invite.Accepted,
	}

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

func (r *GormRepository) AcceptInvite(invite *types.Invite, userId string) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Update invite
		if err := tx.Model(&models.Invite{}).
			Where("id = ?", []byte(invite.Id)).
			Update("accepted", true).Error; err != nil {
			return err
		}

		// Create userorg association
		userOrg := &models.UserOrg{
			UserID: []byte(userId),
			OrgID:  []byte(invite.OrgId),
			Admin:  false,
		}

		return tx.Create(userOrg).Error
	})
}

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

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

	invites := make([]*types.Invite, len(gormInvites))
	for i, gormInvite := range gormInvites {
		invites[i] = r.convertGormInviteToTypesInvite(&gormInvite)
	}

	return invites, nil
}

func (r *GormRepository) GetInvite(id string) (*types.Invite, error) {
	var gormInvite models.Invite
	result := r.db.Where("id = ?", id).First(&gormInvite)

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

	return r.convertGormInviteToTypesInvite(&gormInvite), nil
}

func (r *GormRepository) DeleteInvite(id string) error {
	return r.db.Where("id = ?", id).Delete(&models.Invite{}).Error
}

// SessionInterface implementation (basic stubs)
func (r *GormRepository) InsertSession(session *types.Session) error {
	var terminated uint64
	if session.Terminated.Valid {
		terminated = uint64(util.TimeToMs(session.Terminated.Time))
	}
	
	gormSession := &models.Session{
		ID:         []byte(session.Id),
		UserID:     []byte(session.UserId),
		Inserted:   uint64(util.TimeToMs(time.Now())),
		Updated:    uint64(util.TimeToMs(time.Now())),
		Terminated: terminated,
	}
	return r.db.Create(gormSession).Error
}

func (r *GormRepository) TerminateSession(sessionId string) error {
	return r.db.Model(&models.Session{}).
		Where("id = ?", []byte(sessionId)).
		Update("terminated", uint64(util.TimeToMs(time.Now()))).Error
}

func (r *GormRepository) DeleteSession(sessionId, userId string) error {
	return r.db.Where("id = ? AND user_id = ?", []byte(sessionId), []byte(userId)).
		Delete(&models.Session{}).Error
}

func (r *GormRepository) UpdateSessionActivity(sessionId string) error {
	return r.db.Model(&models.Session{}).
		Where("id = ?", []byte(sessionId)).
		Update("updated", uint64(util.TimeToMs(time.Now()))).Error
}

// APIKey interface (basic stubs)
func (r *GormRepository) InsertApiKey(apiKey *types.ApiKey) error {
	gormApiKey := &models.APIKey{
		ID:       []byte(apiKey.Id),
		UserID:   []byte(apiKey.UserId),
		Inserted: uint64(util.TimeToMs(time.Now())),
		Updated:  uint64(util.TimeToMs(time.Now())),
		Label:    apiKey.Label,
	}
	return r.db.Create(gormApiKey).Error
}

func (r *GormRepository) DeleteApiKey(keyId, userId string) error {
	return r.db.Where("id = ? AND user_id = ?", []byte(keyId), []byte(userId)).
		Delete(&models.APIKey{}).Error
}

func (r *GormRepository) UpdateApiKey(apiKey *types.ApiKey) error {
	return r.db.Model(&models.APIKey{}).
		Where("id = ?", []byte(apiKey.Id)).
		Updates(map[string]interface{}{
			"updated": uint64(util.TimeToMs(time.Now())),
			"label":   apiKey.Label,
		}).Error
}

func (r *GormRepository) GetApiKeys(userId string) ([]*types.ApiKey, error) {
	var gormApiKeys []models.APIKey
	result := r.db.Where("user_id = ? AND deleted_at IS NULL", []byte(userId)).
		Find(&gormApiKeys)

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

	apiKeys := make([]*types.ApiKey, len(gormApiKeys))
	for i, gormApiKey := range gormApiKeys {
		apiKeys[i] = r.convertGormApiKeyToTypesApiKey(&gormApiKey)
	}

	return apiKeys, nil
}

func (r *GormRepository) UpdateApiKeyActivity(keyId string) error {
	return r.db.Model(&models.APIKey{}).
		Where("id = ?", []byte(keyId)).
		Update("updated", uint64(util.TimeToMs(time.Now()))).Error
}

func (r *GormRepository) convertGormApiKeyToTypesApiKey(gormApiKey *models.APIKey) *types.ApiKey {
	return &types.ApiKey{
		Id:       string(gormApiKey.ID),
		Inserted: util.MsToTime(int64(gormApiKey.Inserted)),
		Updated:  util.MsToTime(int64(gormApiKey.Updated)),
		UserId:   string(gormApiKey.UserID),
		Label:    gormApiKey.Label,
	}
}

// TransactionInterface implementation
func (r *GormRepository) InsertTransaction(transaction *types.Transaction) error {
	gormTransaction := &models.Transaction{
		ID:          []byte(transaction.Id),
		OrgID:       []byte(transaction.OrgId),
		UserID:      []byte(transaction.UserId),
		Date:        uint64(transaction.Date.Unix()),
		Inserted:    uint64(util.TimeToMs(time.Now())),
		Updated:     uint64(util.TimeToMs(time.Now())),
		Description: transaction.Description,
		Data:        transaction.Data,
	}
	return r.db.Create(gormTransaction).Error
}

func (r *GormRepository) GetTransactionById(id string) (*types.Transaction, error) {
	var gormTransaction models.Transaction
	result := r.db.Where("id = ?", []byte(id)).First(&gormTransaction)
	if result.Error != nil {
		return nil, result.Error
	}
	return r.convertGormTransactionToTypesTransaction(&gormTransaction), nil
}

func (r *GormRepository) GetTransactionsByAccount(accountId string, options *types.QueryOptions) ([]*types.Transaction, error) {
	var gormTransactions []models.Transaction
	query := r.db.Table("transactions").
		Joins("JOIN splits ON splits.transaction_id = transactions.id").
		Where("splits.account_id = ?", []byte(accountId))
	
	if options != nil {
		// Apply query options like limit, skip, date range, etc.
		if options.Limit > 0 {
			query = query.Limit(int(options.Limit))
		}
		if options.Skip > 0 {
			query = query.Offset(int(options.Skip))
		}
	}
	
	result := query.Find(&gormTransactions)
	if result.Error != nil {
		return nil, result.Error
	}
	
	transactions := make([]*types.Transaction, len(gormTransactions))
	for i, gormTx := range gormTransactions {
		transactions[i] = r.convertGormTransactionToTypesTransaction(&gormTx)
	}
	
	return transactions, nil
}

func (r *GormRepository) GetTransactionsByOrg(orgId string, options *types.QueryOptions, accountIds []string) ([]*types.Transaction, error) {
	var gormTransactions []models.Transaction
	query := r.db.Where("org_id = ?", []byte(orgId))
	
	if len(accountIds) > 0 {
		// Convert string IDs to byte arrays
		byteAccountIds := make([][]byte, len(accountIds))
		for i, id := range accountIds {
			byteAccountIds[i] = []byte(id)
		}
		query = query.Where("id IN (SELECT DISTINCT transaction_id FROM splits WHERE account_id IN ?)", byteAccountIds)
	}
	
	if options != nil {
		if options.Limit > 0 {
			query = query.Limit(int(options.Limit))
		}
		if options.Skip > 0 {
			query = query.Offset(int(options.Skip))
		}
	}
	
	result := query.Find(&gormTransactions)
	if result.Error != nil {
		return nil, result.Error
	}
	
	transactions := make([]*types.Transaction, len(gormTransactions))
	for i, gormTx := range gormTransactions {
		transactions[i] = r.convertGormTransactionToTypesTransaction(&gormTx)
	}
	
	return transactions, nil
}

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

func (r *GormRepository) DeleteAndInsertTransaction(id string, transaction *types.Transaction) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Delete the old transaction
		if err := tx.Where("id = ?", []byte(id)).Delete(&models.Transaction{}).Error; err != nil {
			return err
		}
		
		// Insert the new transaction
		gormTransaction := &models.Transaction{
			ID:          []byte(transaction.Id),
			OrgID:       []byte(transaction.OrgId),
			UserID:      []byte(transaction.UserId),
			Date:        uint64(transaction.Date.Unix()),
			Inserted:    uint64(util.TimeToMs(time.Now())),
			Updated:     uint64(util.TimeToMs(time.Now())),
			Description: transaction.Description,
			Data:        transaction.Data,
		}
		
		return tx.Create(gormTransaction).Error
	})
}

func (r *GormRepository) convertGormTransactionToTypesTransaction(gormTx *models.Transaction) *types.Transaction {
	return &types.Transaction{
		Id:          string(gormTx.ID),
		OrgId:       string(gormTx.OrgID),
		UserId:      string(gormTx.UserID),
		Date:        time.Unix(int64(gormTx.Date), 0),
		Inserted:    util.MsToTime(int64(gormTx.Inserted)),
		Updated:     util.MsToTime(int64(gormTx.Updated)),
		Description: gormTx.Description,
		Data:        gormTx.Data,
		Deleted:     gormTx.Deleted,
	}
}

// Helper conversion functions
func (r *GormRepository) convertGormOrgToTypesOrg(gormOrg *models.Org) *types.Org {
	return &types.Org{
		Id:        string(gormOrg.ID),
		Inserted:  util.MsToTime(int64(gormOrg.Inserted)),
		Updated:   util.MsToTime(int64(gormOrg.Updated)),
		Name:      gormOrg.Name,
		Currency:  gormOrg.Currency,
		Precision: gormOrg.Precision,
		Timezone:  gormOrg.Timezone,
	}
}

func (r *GormRepository) convertGormInviteToTypesInvite(gormInvite *models.Invite) *types.Invite {
	return &types.Invite{
		Id:       string(gormInvite.ID),
		OrgId:    string(gormInvite.OrgID),
		Inserted: util.MsToTime(int64(gormInvite.Inserted)),
		Updated:  util.MsToTime(int64(gormInvite.Updated)),
		Email:    gormInvite.Email,
		Accepted: gormInvite.Accepted,
	}
}

// Stub implementations for remaining interfaces that aren't fully used yet
func (r *GormRepository) GetPrices(orgId string, date time.Time) ([]*types.Price, error) {
	// Stub implementation - add as needed
	return nil, nil
}

func (r *GormRepository) InsertPrice(price *types.Price) error {
	// Stub implementation - add as needed
	return nil
}

func (r *GormRepository) Ping() error {
	// Check if the database connection is alive
	sqlDB, err := r.db.DB()
	if err != nil {
		return err
	}
	return sqlDB.Ping()
}

func (r *GormRepository) InsertBudget(budget *types.Budget) error {
	// Stub implementation - add as needed  
	return nil
}

func (r *GormRepository) GetBudgets(orgId string) ([]*types.Budget, error) {
	// Stub implementation - add as needed
	return nil, nil
}