// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package federationdomainproviders

import (
	"fmt"
	"net/url"
	"strings"

	"go.pinniped.dev/internal/constable"
)

// FederationDomainIssuer is a parsed FederationDomain representing all the settings for a downstream OIDC provider
// and contains configuration representing a set of upstream identity providers.
type FederationDomainIssuer struct {
	issuer     string
	issuerHost string
	issuerPath string

	// identityProviders should be used when they are explicitly specified in the FederationDomain's spec.
	identityProviders []*FederationDomainIdentityProvider
	// defaultIdentityProvider should be used only for the backwards compatibility mode where identity providers
	// are not explicitly specified in the FederationDomain's spec, and there is exactly one IDP CR defined in the
	// Supervisor's namespace.
	defaultIdentityProvider *FederationDomainIdentityProvider
}

// NewFederationDomainIssuer returns a FederationDomainIssuer.
// Performs validation, and returns any error from validation.
func NewFederationDomainIssuer(
	issuer string,
	identityProviders []*FederationDomainIdentityProvider,
) (*FederationDomainIssuer, error) {
	p := FederationDomainIssuer{issuer: issuer, identityProviders: identityProviders}
	err := p.validateURL()
	if err != nil {
		return nil, err
	}
	return &p, nil
}

func NewFederationDomainIssuerWithDefaultIDP(
	issuer string,
	defaultIdentityProvider *FederationDomainIdentityProvider,
) (*FederationDomainIssuer, error) {
	fdi, err := NewFederationDomainIssuer(issuer, []*FederationDomainIdentityProvider{defaultIdentityProvider})
	if err != nil {
		return nil, err
	}
	fdi.defaultIdentityProvider = defaultIdentityProvider
	return fdi, nil
}

func (p *FederationDomainIssuer) validateURL() error {
	if p.issuer == "" {
		return constable.Error("federation domain must have an issuer")
	}

	issuerURL, err := url.Parse(p.issuer)
	if err != nil {
		return fmt.Errorf("could not parse issuer as URL: %w", err)
	}

	if issuerURL.Scheme != "https" {
		return constable.Error(`issuer must have "https" scheme`)
	}

	if issuerURL.Hostname() == "" {
		return constable.Error(`issuer must have a hostname`)
	}

	if issuerURL.User != nil {
		return constable.Error(`issuer must not have username or password`)
	}

	if strings.HasSuffix(issuerURL.Path, "/") {
		return constable.Error(`issuer must not have trailing slash in path`)
	}

	if issuerURL.RawQuery != "" {
		return constable.Error(`issuer must not have query`)
	}

	if issuerURL.Fragment != "" {
		return constable.Error(`issuer must not have fragment`)
	}

	p.issuerHost = issuerURL.Host
	p.issuerPath = issuerURL.Path

	return nil
}

// Issuer returns the issuer.
func (p *FederationDomainIssuer) Issuer() string {
	return p.issuer
}

// IssuerHost returns the issuerHost.
func (p *FederationDomainIssuer) IssuerHost() string {
	return p.issuerHost
}

// IssuerPath returns the issuerPath.
func (p *FederationDomainIssuer) IssuerPath() string {
	return p.issuerPath
}

// IdentityProviders returns the IdentityProviders.
func (p *FederationDomainIssuer) IdentityProviders() []*FederationDomainIdentityProvider {
	return p.identityProviders
}

// DefaultIdentityProvider will return nil when there is no default.
func (p *FederationDomainIssuer) DefaultIdentityProvider() *FederationDomainIdentityProvider {
	return p.defaultIdentityProvider
}
