package service

import (
	"baron-sso-backend/internal/domain"
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/descope/go-sdk/descope"
	"github.com/descope/go-sdk/descope/client"
)

type DescopeProvider struct {
	Client       *client.DescopeClient
	FrontendURL  string
	fieldMapping map[string]string // Key: Broker Field Name, Value: Descope Attribute Key
}

func NewDescopeProvider(projectID, managementKey string) *DescopeProvider {
	var descopeClient *client.DescopeClient
	var err error
	if projectID != "" {
		descopeClient, err = client.NewWithConfig(&client.Config{
			ProjectID:     projectID,
			ManagementKey: managementKey,
		})
		if err != nil {
			slog.Warn("Failed to initialize Descope Client in Provider", "error", err)
		}
	}

	// Define the mapping between BrokerUser fields and Descope attributes.
	// In a real scenario, this could be loaded from a config file.
	// For this implementation, we hardcode the support to demonstrate the validation.
	// We map the Broker's required custom attributes to Descope's keys.
	mapping := map[string]string{
		"grade":      "customAttributes.userRank", // Broker 'grade' maps to Descope 'userRank'
		"department": "customAttributes.dept",     // Broker 'department' maps to Descope 'dept'
	}

	return &DescopeProvider{
		Client:       descopeClient,
		FrontendURL:  os.Getenv("USERFRONT_URL"),
		fieldMapping: mapping,
	}
}

func (d *DescopeProvider) Name() string {
	return "Descope"
}

// GetMetadata returns the schema support information.
// Currently, it returns the standard fields Descope supports + the mapped custom attributes.
func (d *DescopeProvider) GetMetadata() (*domain.IDPMetadata, error) {
	// 1. Standard Fields supported by Descope
	supported := []string{"id", "email", "name", "phone_number"}

	// 2. Add mapped custom attributes
	// The Validator checks if the Broker's required keys (e.g., "grade") are present in this list.
	for brokerKey := range d.fieldMapping {
		supported = append(supported, brokerKey)
	}

	return &domain.IDPMetadata{
		SupportedFields: supported,
	}, nil
}

// CreateUser는 Descope Management API를 사용해 사용자를 생성합니다.
func (d *DescopeProvider) CreateUser(user *domain.BrokerUser, password string) (string, error) {
	if d.Client == nil {
		return "", fmt.Errorf("descope provider: client is nil")
	}
	if user == nil {
		return "", fmt.Errorf("descope provider: user payload is nil")
	}
	if user.Email == "" || password == "" {
		return "", fmt.Errorf("descope provider: email and password are required")
	}

	normalizedPhone := user.PhoneNumber
	normalizedPhone = strings.ReplaceAll(normalizedPhone, "-", "")
	normalizedPhone = strings.ReplaceAll(normalizedPhone, " ", "")
	if strings.HasPrefix(normalizedPhone, "010") {
		normalizedPhone = "+82" + normalizedPhone[1:]
	} else if strings.HasPrefix(normalizedPhone, "82") {
		normalizedPhone = "+" + normalizedPhone
	}

	// 존재 여부 확인
	exists, _ := d.Client.Management.User().Load(context.Background(), user.Email)
	if exists != nil {
		return "", fmt.Errorf("descope provider: user already exists")
	}

	descopeUser := &descope.UserRequest{}
	descopeUser.Email = user.Email
	descopeUser.Phone = normalizedPhone
	descopeUser.Name = user.Name
	descopeUser.CustomAttributes = map[string]any{}
	for k, v := range user.Attributes {
		descopeUser.CustomAttributes[k] = v
	}
	descopeUser.CustomAttributes["createdAt"] = time.Now().Format(time.RFC3339)

	if _, err := d.Client.Management.User().Create(context.Background(), user.Email, descopeUser); err != nil {
		return "", fmt.Errorf("descope provider: create user failed: %w", err)
	}
	if err := d.Client.Management.User().SetPassword(context.Background(), user.Email, password); err != nil {
		_ = d.Client.Management.User().Delete(context.Background(), user.Email)
		return "", fmt.Errorf("descope provider: set password failed: %w", err)
	}

	slog.Info("Descope user created", "email", user.Email)
	return user.Email, nil
}

// SignIn은 Descope Password 로그인 후 세션 토큰을 반환합니다.
func (d *DescopeProvider) SignIn(loginID, password string) (*domain.AuthInfo, error) {
	if d.Client == nil {
		return nil, fmt.Errorf("descope provider: client is nil")
	}
	authInfo, err := d.Client.Auth.Password().SignIn(context.Background(), loginID, password, nil)
	if err != nil {
		return nil, err
	}

	res := &domain.AuthInfo{
		SessionToken: &domain.Token{
			JWT:        authInfo.SessionToken.JWT,
			Expiration: time.Unix(authInfo.SessionToken.Expiration, 0),
		},
		Subject: authInfo.User.UserID,
	}
	if authInfo.RefreshToken != nil {
		res.RefreshToken = &domain.Token{
			JWT:        authInfo.RefreshToken.JWT,
			Expiration: time.Unix(authInfo.RefreshToken.Expiration, 0),
		}
	}
	return res, nil
}

func (d *DescopeProvider) InitiatePasswordReset(loginID, redirectUrl string) error {
	ctx := context.Background()
	err := d.Client.Auth.Password().SendPasswordReset(ctx, loginID, redirectUrl, nil)
	if err != nil {
		slog.Error("Descope SendPasswordReset failed (raw)",
			"loginID", loginID,
			"redirectUrl", redirectUrl,
			"err", err,
			"err_type", fmt.Sprintf("%T", err),
		)

		if de, ok := err.(*descope.Error); ok {
			status := de.Info[descope.ErrorInfoKeys.HTTPResponseStatusCode] // "Status-Code"
			slog.Error("Descope error details",
				"code", de.Code,
				"description", de.Description,
				"message", de.Message,
				"status_code", status,
				"info", de.Info,
			)
		}
	}
	return err
}

func (d *DescopeProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) {
	ctx := context.Background()
	authInfo, err := d.Client.Auth.MagicLink().Verify(ctx, token, nil)
	if err != nil {
		return nil, err
	}

	res := &domain.AuthInfo{
		SessionToken: &domain.Token{
			JWT:        authInfo.SessionToken.JWT,
			Expiration: time.Unix(authInfo.SessionToken.Expiration, 0),
		},
	}
	if authInfo.RefreshToken != nil {
		res.RefreshToken = &domain.Token{
			JWT:        authInfo.RefreshToken.JWT,
			Expiration: time.Unix(authInfo.RefreshToken.Expiration, 0),
		}
	}

	return res, nil
}

func (d *DescopeProvider) UpdateUserPassword(loginID, newPassword string, r *http.Request) error {
	ctx := context.Background()
	return d.Client.Auth.Password().UpdateUserPassword(ctx, loginID, newPassword, r)
}
