package token

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/kubelogin/pkg/internal/pop"
	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)

type InteractiveBrowserCredentialWithPoP struct {
	popClaims   map[string]string
	client      public.Client
	options     *pop.MsalClientOptions
	keyProvider PoPKeyProvider
}

var _ CredentialProvider = (*InteractiveBrowserCredentialWithPoP)(nil)

func newInteractiveBrowserCredentialWithPoP(opts *Options) (CredentialProvider, error) {
	if opts.ClientID == "" {
		return nil, fmt.Errorf("client ID cannot be empty")
	}
	if opts.TenantID == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}
	popClaimsMap, err := parsePoPClaims(opts.PoPTokenClaims)
	if err != nil {
		return nil, fmt.Errorf("unable to parse PoP claims: %w", err)
	}
	if len(popClaimsMap) == 0 {
		return nil, fmt.Errorf("number of pop claims is invalid: %d", len(popClaimsMap))
	}

	// Construct authority URL properly to avoid malformation
	authorityURL, err := url.JoinPath(opts.GetCloudConfiguration().ActiveDirectoryAuthorityHost, opts.TenantID)
	if err != nil {
		return nil, fmt.Errorf("unable to construct authority URL: %w", err)
	}

	msalOpts := &pop.MsalClientOptions{
		Authority:                authorityURL,
		ClientID:                 opts.ClientID,
		TenantID:                 opts.TenantID,
		DisableInstanceDiscovery: opts.DisableInstanceDiscovery,
	}
	if opts.httpClient != nil {
		msalOpts.Options.Transport = opts.httpClient
	}

	// Get cache from Options
	popCache := opts.GetPoPTokenCache()

	client, err := pop.NewPublicClient(
		msalOpts,
		pop.WithCustomCachePublic(popCache),
	)
	if err != nil {
		return nil, fmt.Errorf("unable to create public client: %w", err)
	}

	return &InteractiveBrowserCredentialWithPoP{
		options:     msalOpts,
		client:      client,
		popClaims:   popClaimsMap,
		keyProvider: opts.GetPoPKeyProvider(),
	}, nil
}

func (c *InteractiveBrowserCredentialWithPoP) Name() string {
	return "InteractiveBrowserCredentialWithPoP"
}

func (c *InteractiveBrowserCredentialWithPoP) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (azidentity.AuthenticationRecord, error) {
	return azidentity.AuthenticationRecord{}, errAuthenticateNotSupported
}

func (c *InteractiveBrowserCredentialWithPoP) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
	// Get PoP key using centralized key provider
	popKey, err := c.keyProvider.GetPoPKey()
	if err != nil {
		return azcore.AccessToken{}, err
	}

	token, expirationTimeUnix, err := pop.AcquirePoPTokenInteractive(
		ctx,
		c.popClaims,
		opts.Scopes,
		c.client,
		c.options,
		popKey,
	)
	if err != nil {
		return azcore.AccessToken{}, fmt.Errorf("failed to create PoP token using interactive login: %w", err)
	}
	return azcore.AccessToken{Token: token, ExpiresOn: time.Unix(expirationTimeUnix, 0)}, nil
}

func (c *InteractiveBrowserCredentialWithPoP) NeedAuthenticate() bool {
	return false
}
