package server

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/dexidp/dex/storage"
	"github.com/rs/xid"
	log "github.com/sirupsen/logrus"

	"github.com/netbirdio/netbird/idp/dex"
	"github.com/netbirdio/netbird/management/server/activity"
	"github.com/netbirdio/netbird/management/server/idp"
	"github.com/netbirdio/netbird/management/server/permissions/modules"
	"github.com/netbirdio/netbird/management/server/permissions/operations"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/shared/management/status"
)

// oidcProviderJSON represents the OpenID Connect discovery document
type oidcProviderJSON struct {
	Issuer string `json:"issuer"`
}

// validateOIDCIssuer validates the OIDC issuer by fetching the OpenID configuration
// and verifying that the returned issuer matches the configured one.
func validateOIDCIssuer(ctx context.Context, issuer string) error {
	wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"

	httpClient := &http.Client{
		Timeout: 10 * time.Second,
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, wellKnown, nil)
	if err != nil {
		return fmt.Errorf("%w: %v", types.ErrIdentityProviderIssuerUnreachable, err)
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("%w: %v", types.ErrIdentityProviderIssuerUnreachable, err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("%w: unable to read response body: %v", types.ErrIdentityProviderIssuerUnreachable, err)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("%w: %s: %s", types.ErrIdentityProviderIssuerUnreachable, resp.Status, body)
	}

	var p oidcProviderJSON
	if err := json.Unmarshal(body, &p); err != nil {
		return fmt.Errorf("%w: failed to decode provider discovery object: %v", types.ErrIdentityProviderIssuerUnreachable, err)
	}

	if p.Issuer != issuer {
		return fmt.Errorf("%w: expected %q got %q", types.ErrIdentityProviderIssuerMismatch, issuer, p.Issuer)
	}

	return nil
}

// validateIdentityProviderConfig validates the identity provider configuration including
// basic validation and OIDC issuer verification.
func validateIdentityProviderConfig(ctx context.Context, idpConfig *types.IdentityProvider) error {
	if err := idpConfig.Validate(); err != nil {
		return status.Errorf(status.InvalidArgument, "%s", err.Error())
	}

	// Validate the issuer by calling the OIDC discovery endpoint
	if idpConfig.Issuer != "" {
		if err := validateOIDCIssuer(ctx, idpConfig.Issuer); err != nil {
			return status.Errorf(status.InvalidArgument, "%s", err.Error())
		}
	}

	return nil
}

// GetIdentityProviders returns all identity providers for an account
func (am *DefaultAccountManager) GetIdentityProviders(ctx context.Context, accountID, userID string) ([]*types.IdentityProvider, error) {
	ok, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.IdentityProviders, operations.Read)
	if err != nil {
		return nil, status.NewPermissionValidationError(err)
	}
	if !ok {
		return nil, status.NewPermissionDeniedError()
	}

	embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
	if !ok {
		log.Warn("identity provider management requires embedded IdP")
		return []*types.IdentityProvider{}, nil
	}

	connectors, err := embeddedManager.ListConnectors(ctx)
	if err != nil {
		return nil, status.Errorf(status.Internal, "failed to list identity providers: %v", err)
	}

	result := make([]*types.IdentityProvider, 0, len(connectors))
	for _, conn := range connectors {
		result = append(result, connectorConfigToIdentityProvider(conn, accountID))
	}

	return result, nil
}

// GetIdentityProvider returns a specific identity provider by ID
func (am *DefaultAccountManager) GetIdentityProvider(ctx context.Context, accountID, idpID, userID string) (*types.IdentityProvider, error) {
	ok, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.IdentityProviders, operations.Read)
	if err != nil {
		return nil, status.NewPermissionValidationError(err)
	}
	if !ok {
		return nil, status.NewPermissionDeniedError()
	}

	embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
	if !ok {
		return nil, status.Errorf(status.Internal, "identity provider management requires embedded IdP")
	}

	conn, err := embeddedManager.GetConnector(ctx, idpID)
	if err != nil {
		if errors.Is(err, storage.ErrNotFound) {
			return nil, status.Errorf(status.NotFound, "identity provider not found")
		}
		return nil, status.Errorf(status.Internal, "failed to get identity provider: %v", err)
	}

	return connectorConfigToIdentityProvider(conn, accountID), nil
}

// CreateIdentityProvider creates a new identity provider
func (am *DefaultAccountManager) CreateIdentityProvider(ctx context.Context, accountID, userID string, idpConfig *types.IdentityProvider) (*types.IdentityProvider, error) {
	ok, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.IdentityProviders, operations.Create)
	if err != nil {
		return nil, status.NewPermissionValidationError(err)
	}
	if !ok {
		return nil, status.NewPermissionDeniedError()
	}

	if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
		return nil, err
	}

	embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
	if !ok {
		return nil, status.Errorf(status.Internal, "identity provider management requires embedded IdP")
	}

	// Generate ID if not provided
	if idpConfig.ID == "" {
		idpConfig.ID = generateIdentityProviderID(idpConfig.Type)
	}
	idpConfig.AccountID = accountID

	connCfg := identityProviderToConnectorConfig(idpConfig)

	_, err = embeddedManager.CreateConnector(ctx, connCfg)
	if err != nil {
		return nil, status.Errorf(status.Internal, "failed to create identity provider: %v", err)
	}

	am.StoreEvent(ctx, userID, idpConfig.ID, accountID, activity.IdentityProviderCreated, idpConfig.EventMeta())

	return idpConfig, nil
}

// UpdateIdentityProvider updates an existing identity provider
func (am *DefaultAccountManager) UpdateIdentityProvider(ctx context.Context, accountID, idpID, userID string, idpConfig *types.IdentityProvider) (*types.IdentityProvider, error) {
	ok, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.IdentityProviders, operations.Update)
	if err != nil {
		return nil, status.NewPermissionValidationError(err)
	}
	if !ok {
		return nil, status.NewPermissionDeniedError()
	}

	if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
		return nil, err
	}

	embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
	if !ok {
		return nil, status.Errorf(status.Internal, "identity provider management requires embedded IdP")
	}

	idpConfig.ID = idpID
	idpConfig.AccountID = accountID

	connCfg := identityProviderToConnectorConfig(idpConfig)

	if err := embeddedManager.UpdateConnector(ctx, connCfg); err != nil {
		return nil, status.Errorf(status.Internal, "failed to update identity provider: %v", err)
	}

	am.StoreEvent(ctx, userID, idpConfig.ID, accountID, activity.IdentityProviderUpdated, idpConfig.EventMeta())

	return idpConfig, nil
}

// DeleteIdentityProvider deletes an identity provider
func (am *DefaultAccountManager) DeleteIdentityProvider(ctx context.Context, accountID, idpID, userID string) error {
	ok, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.IdentityProviders, operations.Delete)
	if err != nil {
		return status.NewPermissionValidationError(err)
	}
	if !ok {
		return status.NewPermissionDeniedError()
	}

	embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
	if !ok {
		return status.Errorf(status.Internal, "identity provider management requires embedded IdP")
	}

	// Get the IDP info before deleting for the activity event
	conn, err := embeddedManager.GetConnector(ctx, idpID)
	if err != nil {
		if errors.Is(err, storage.ErrNotFound) {
			return status.Errorf(status.NotFound, "identity provider not found")
		}
		return status.Errorf(status.Internal, "failed to get identity provider: %v", err)
	}
	idpConfig := connectorConfigToIdentityProvider(conn, accountID)

	if err := embeddedManager.DeleteConnector(ctx, idpID); err != nil {
		if errors.Is(err, storage.ErrNotFound) {
			return status.Errorf(status.NotFound, "identity provider not found")
		}
		return status.Errorf(status.Internal, "failed to delete identity provider: %v", err)
	}

	am.StoreEvent(ctx, userID, idpID, accountID, activity.IdentityProviderDeleted, idpConfig.EventMeta())

	return nil
}

// connectorConfigToIdentityProvider converts a dex.ConnectorConfig to types.IdentityProvider
func connectorConfigToIdentityProvider(conn *dex.ConnectorConfig, accountID string) *types.IdentityProvider {
	return &types.IdentityProvider{
		ID:           conn.ID,
		AccountID:    accountID,
		Type:         types.IdentityProviderType(conn.Type),
		Name:         conn.Name,
		Issuer:       conn.Issuer,
		ClientID:     conn.ClientID,
		ClientSecret: conn.ClientSecret,
	}
}

// identityProviderToConnectorConfig converts a types.IdentityProvider to dex.ConnectorConfig
func identityProviderToConnectorConfig(idpConfig *types.IdentityProvider) *dex.ConnectorConfig {
	return &dex.ConnectorConfig{
		ID:           idpConfig.ID,
		Name:         idpConfig.Name,
		Type:         string(idpConfig.Type),
		Issuer:       idpConfig.Issuer,
		ClientID:     idpConfig.ClientID,
		ClientSecret: idpConfig.ClientSecret,
	}
}

// generateIdentityProviderID generates a unique ID for an identity provider.
// For specific provider types (okta, zitadel, entra, google, pocketid, microsoft, adfs),
// the ID is prefixed with the type name. Generic OIDC providers get no prefix.
func generateIdentityProviderID(idpType types.IdentityProviderType) string {
	id := xid.New().String()

	switch idpType {
	case types.IdentityProviderTypeOkta:
		return "okta-" + id
	case types.IdentityProviderTypeZitadel:
		return "zitadel-" + id
	case types.IdentityProviderTypeEntra:
		return "entra-" + id
	case types.IdentityProviderTypeGoogle:
		return "google-" + id
	case types.IdentityProviderTypePocketID:
		return "pocketid-" + id
	case types.IdentityProviderTypeMicrosoft:
		return "microsoft-" + id
	case types.IdentityProviderTypeAuthentik:
		return "authentik-" + id
	case types.IdentityProviderTypeKeycloak:
		return "keycloak-" + id
	case types.IdentityProviderTypeADFS:
		return "adfs-" + id
	default:
		// Generic OIDC - no prefix
		return id
	}
}
