package user

import (
	"context"
	"fmt"
	"time"

	"github.com/yaoapp/gou/model"
	"github.com/yaoapp/kun/maps"
)

// Invitation Code Resource (Official Platform Invitation Codes)

// invitationCodeExists checks if an invitation code exists by code
func (u *DefaultUser) invitationCodeExists(ctx context.Context, code string) (bool, error) {
	m := model.Select(u.invitationModel)
	invitations, err := m.Get(model.QueryParam{
		Select: []interface{}{"id"},
		Wheres: []model.QueryWhere{
			{Column: "code", Value: code},
		},
		Limit: 1,
	})

	if err != nil {
		return false, fmt.Errorf("failed to check invitation code existence: %w", err)
	}

	return len(invitations) > 0, nil
}

// CreateInvitationCodes creates invitation codes in batch
// Supports creating multiple invitation codes at once for efficiency
func (u *DefaultUser) CreateInvitationCodes(ctx context.Context, codeData []maps.MapStrAny) ([]string, error) {
	if len(codeData) == 0 {
		return []string{}, nil
	}

	codes := make([]string, 0, len(codeData))
	m := model.Select(u.invitationModel)

	// Validate and prepare data - collect all possible columns
	columnsSet := make(map[string]bool)
	columnsSet["code"] = true
	columnsSet["status"] = true
	columnsSet["is_published"] = true
	columnsSet["code_type"] = true

	for i := range codeData {
		// Validate required fields
		code, hasCode := codeData[i]["code"].(string)
		if !hasCode || code == "" {
			return nil, fmt.Errorf("code is required in codeData at index %d", i)
		}

		// Set default values if not provided
		if _, exists := codeData[i]["status"]; !exists {
			codeData[i]["status"] = "draft"
		}
		if _, exists := codeData[i]["is_published"]; !exists {
			codeData[i]["is_published"] = false
		}
		if _, exists := codeData[i]["code_type"]; !exists {
			codeData[i]["code_type"] = "official"
		}

		// Collect optional columns
		for _, col := range []string{"owner_id", "description", "source", "expires_at", "metadata"} {
			if _, exists := codeData[i][col]; exists {
				columnsSet[col] = true
			}
		}

		codes = append(codes, code)
	}

	// Build ordered column list
	columns := []string{"code", "status", "is_published", "code_type"}
	for _, col := range []string{"owner_id", "description", "source", "expires_at", "metadata"} {
		if columnsSet[col] {
			columns = append(columns, col)
		}
	}

	// Build values matrix
	values := make([][]interface{}, 0, len(codeData))
	for i := range codeData {
		row := make([]interface{}, len(columns))
		for j, col := range columns {
			if val, exists := codeData[i][col]; exists {
				row[j] = val
			} else {
				row[j] = nil
			}
		}
		values = append(values, row)
	}

	// Batch insert
	err := m.Insert(columns, values)
	if err != nil {
		return nil, fmt.Errorf(ErrFailedToCreateInvitationCode, err)
	}

	return codes, nil
}

// UseInvitationCode marks an invitation code as used (redemption)
// This is called when a user successfully uses an invitation code during registration
func (u *DefaultUser) UseInvitationCode(ctx context.Context, code string, userID string) error {
	m := model.Select(u.invitationModel)

	// First, get the invitation code to validate it
	invitations, err := m.Get(model.QueryParam{
		Select: []interface{}{"id", "code", "status", "is_published", "expires_at", "used_by"},
		Wheres: []model.QueryWhere{
			{Column: "code", Value: code},
		},
		Limit: 1,
	})

	if err != nil {
		return fmt.Errorf(ErrFailedToUseInvitationCode, err)
	}

	if len(invitations) == 0 {
		return fmt.Errorf(ErrInvitationCodeNotFound)
	}

	invitation := invitations[0]

	// Check if already used
	if usedBy := invitation["used_by"]; usedBy != nil && usedBy != "" {
		return fmt.Errorf(ErrInvitationCodeAlreadyUsed)
	}

	// Check if published (handle both bool and int64 types from different databases)
	isPublished := false
	switch v := invitation["is_published"].(type) {
	case bool:
		isPublished = v
	case int64:
		isPublished = v != 0
	case int:
		isPublished = v != 0
	}
	if !isPublished {
		return fmt.Errorf(ErrInvitationCodeNotPublished)
	}

	// Check status
	status, ok := invitation["status"].(string)
	if !ok || status != "active" {
		return fmt.Errorf("invitation code status must be 'active' to use, current status: %s", status)
	}

	// Check if expired
	if expiresAt := invitation["expires_at"]; expiresAt != nil {
		if expired, err := checkTimeExpired(expiresAt); err == nil && expired {
			return fmt.Errorf(ErrInvitationCodeExpired)
		}
	}

	// Mark as used
	updateData := maps.MapStrAny{
		"used_by": userID,
		"used_at": time.Now(),
		"status":  "used",
	}

	affected, err := m.UpdateWhere(model.QueryParam{
		Wheres: []model.QueryWhere{
			{Column: "code", Value: code},
		},
		Limit: 1,
	}, updateData)

	if err != nil {
		return fmt.Errorf(ErrFailedToUseInvitationCode, err)
	}

	if affected == 0 {
		// Check if invitation code still exists
		exists, checkErr := u.invitationCodeExists(ctx, code)
		if checkErr != nil {
			return fmt.Errorf(ErrFailedToUseInvitationCode, checkErr)
		}
		if !exists {
			return fmt.Errorf(ErrInvitationCodeNotFound)
		}
		// Invitation code exists but no changes were made (already in this state)
	}

	return nil
}

// DeleteInvitationCode soft deletes an invitation code
func (u *DefaultUser) DeleteInvitationCode(ctx context.Context, code string) error {
	// First check if invitation code exists
	m := model.Select(u.invitationModel)
	invitations, err := m.Get(model.QueryParam{
		Select: []interface{}{"id", "code"},
		Wheres: []model.QueryWhere{
			{Column: "code", Value: code},
		},
		Limit: 1,
	})

	if err != nil {
		return fmt.Errorf(ErrFailedToDeleteInvitationCode, err)
	}

	if len(invitations) == 0 {
		return fmt.Errorf(ErrInvitationCodeNotFound)
	}

	// Proceed with soft delete
	affected, err := m.DeleteWhere(model.QueryParam{
		Wheres: []model.QueryWhere{
			{Column: "code", Value: code},
		},
		Limit: 1,
	})

	if err != nil {
		return fmt.Errorf(ErrFailedToDeleteInvitationCode, err)
	}

	if affected == 0 {
		return fmt.Errorf(ErrInvitationCodeNotFound)
	}

	return nil
}
