package query

import (
	"context"
	"database/sql"
	_ "embed"
	"encoding/json"
	"errors"
	"net/url"
	"time"

	"github.com/zitadel/zitadel/internal/api/authz"
	http_util "github.com/zitadel/zitadel/internal/api/http"
	"github.com/zitadel/zitadel/internal/api/ui/console/path"
	"github.com/zitadel/zitadel/internal/database"
	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/telemetry/tracing"
	"github.com/zitadel/zitadel/internal/zerrors"
)

type OIDCClient struct {
	InstanceID               string                     `json:"instance_id,omitempty"`
	AppID                    string                     `json:"app_id,omitempty"`
	State                    domain.AppState            `json:"state,omitempty"`
	ClientID                 string                     `json:"client_id,omitempty"`
	BackChannelLogoutURI     string                     `json:"back_channel_logout_uri,omitempty"`
	HashedSecret             string                     `json:"client_secret,omitempty"`
	RedirectURIs             []string                   `json:"redirect_uris,omitempty"`
	ResponseTypes            []domain.OIDCResponseType  `json:"response_types,omitempty"`
	GrantTypes               []domain.OIDCGrantType     `json:"grant_types,omitempty"`
	ApplicationType          domain.OIDCApplicationType `json:"application_type,omitempty"`
	AuthMethodType           domain.OIDCAuthMethodType  `json:"auth_method_type,omitempty"`
	PostLogoutRedirectURIs   []string                   `json:"post_logout_redirect_uris,omitempty"`
	IsDevMode                bool                       `json:"is_dev_mode,omitempty"`
	AccessTokenType          domain.OIDCTokenType       `json:"access_token_type,omitempty"`
	AccessTokenRoleAssertion bool                       `json:"access_token_role_assertion,omitempty"`
	IDTokenRoleAssertion     bool                       `json:"id_token_role_assertion,omitempty"`
	IDTokenUserinfoAssertion bool                       `json:"id_token_userinfo_assertion,omitempty"`
	ClockSkew                time.Duration              `json:"clock_skew,omitempty"`
	AdditionalOrigins        []string                   `json:"additional_origins,omitempty"`
	PublicKeys               map[string][]byte          `json:"public_keys,omitempty"`
	ProjectID                string                     `json:"project_id,omitempty"`
	ProjectRoleAssertion     bool                       `json:"project_role_assertion,omitempty"`
	LoginVersion             domain.LoginVersion        `json:"login_version,omitempty"`
	LoginBaseURI             *URL                       `json:"login_base_uri,omitempty"`
	ProjectRoleKeys          []string                   `json:"project_role_keys,omitempty"`
	Settings                 *OIDCSettings              `json:"settings,omitempty"`
}

type URL url.URL

func (c *URL) URL() *url.URL {
	return (*url.URL)(c)
}

func (c *URL) UnmarshalJSON(src []byte) error {
	var s string
	err := json.Unmarshal(src, &s)
	if err != nil {
		return err
	}
	u, err := url.Parse(s)
	if err != nil {
		return err
	}
	*c = URL(*u)
	return nil
}

//go:embed oidc_client_by_id.sql
var oidcClientQuery string

func (q *Queries) ActiveOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
	ctx, span := tracing.NewSpan(ctx)
	defer func() { span.EndWithError(err) }()

	client, err = database.QueryJSONObject[OIDCClient](ctx, q.client, oidcClientQuery,
		authz.GetInstance(ctx).InstanceID(), clientID, getKeys,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, zerrors.ThrowNotFound(err, "QUERY-wu6Ee", "Errors.App.NotFound")
	}
	if err != nil {
		return nil, zerrors.ThrowInternal(err, "QUERY-ieR7R", "Errors.Internal")
	}
	instance := authz.GetInstance(ctx)
	loginV2 := instance.Features().LoginV2
	if loginV2.Required {
		client.LoginVersion = domain.LoginVersion2
		client.LoginBaseURI = (*URL)(loginV2.BaseURI)
	}
	if instance.ConsoleClientID() == clientID {
		client.RedirectURIs = append(client.RedirectURIs, http_util.DomainContext(ctx).Origin()+path.RedirectPath)
		client.PostLogoutRedirectURIs = append(client.PostLogoutRedirectURIs, http_util.DomainContext(ctx).Origin()+path.PostLogoutPath)
	}
	return client, err
}