package auth

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"time"

	"go.b0esche.cloud/backend/internal/config"
	"go.b0esche.cloud/backend/internal/database"

	"github.com/coreos/go-oidc/v3/oidc"
	"golang.org/x/oauth2"
)

type OIDCService struct {
	provider     *oidc.Provider
	oauth2Config oauth2.Config
	db           *database.DB // Assume we have a DB wrapper
}

func NewOIDCService(cfg *config.Config, db *database.DB) (*OIDCService, error) {
	ctx := context.Background()

	provider, err := oidc.NewProvider(ctx, cfg.OIDCIssuerURL)
	if err != nil {
		return nil, fmt.Errorf("failed to get OIDC provider: %w", err)
	}

	oauth2Config := oauth2.Config{
		ClientID:     cfg.OIDCClientID,
		ClientSecret: cfg.OIDCClientSecret,
		RedirectURL:  cfg.OIDCRedirectURL, // Add to config
		Endpoint:     provider.Endpoint(),
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
	}

	return &OIDCService{
		provider:     provider,
		oauth2Config: oauth2Config,
		db:           db,
	}, nil
}

func (s *OIDCService) LoginURL(state string) string {
	return s.oauth2Config.AuthCodeURL(state)
}

func (s *OIDCService) HandleCallback(ctx context.Context, code, state string) (*database.User, *database.Session, error) {
	oauth2Token, err := s.oauth2Config.Exchange(ctx, code)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to exchange code: %w", err)
	}

	rawIDToken, ok := oauth2Token.Extra("id_token").(string)
	if !ok {
		return nil, nil, fmt.Errorf("no id_token in token response")
	}

	idToken, err := s.provider.Verifier(&oidc.Config{ClientID: s.oauth2Config.ClientID}).Verify(ctx, rawIDToken)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to verify ID token: %w", err)
	}

	var claims struct {
		Sub           string `json:"sub"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		Name          string `json:"name"`
	}
	if err := idToken.Claims(&claims); err != nil {
		return nil, nil, fmt.Errorf("failed to parse claims: %w", err)
	}

	user, err := s.db.GetOrCreateUser(ctx, claims.Sub, claims.Email, claims.Name)
	if err != nil {
		return nil, nil, err
	}

	session, err := s.db.CreateSession(ctx, user.ID, time.Now().Add(15*time.Minute))
	if err != nil {
		return nil, nil, err
	}

	return user, session, nil
}

// GenerateState generates a secure random state string
func GenerateState() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}
