package authentik

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

type sourceRef struct {
	PK   string `json:"pk"`
	Slug string `json:"slug"`
	Name string `json:"name"`
}

type brandRef struct {
	PK              string  `json:"pk"`
	BrandUUID       string  `json:"brand_uuid"`
	Domain          string  `json:"domain"`
	FlowEnrollment  *string `json:"flow_enrollment"`
}

type flowInstance struct {
	PK   string `json:"pk"`
	Slug string `json:"slug"`
	Name string `json:"name"`
}

func (c *Client) FindOAuthSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
	q := url.Values{}
	q.Set("slug", slug)
	var out listResponse[sourceRef]
	if err := c.getJSON(ctx, "/api/v3/sources/oauth/?"+q.Encode(), &out); err != nil {
		return nil, false, err
	}
	if len(out.Results) == 0 {
		return nil, false, nil
	}
	return &out.Results[0], true, nil
}

func (c *Client) FindSAMLSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
	q := url.Values{}
	q.Set("slug", slug)
	var out listResponse[sourceRef]
	if err := c.getJSON(ctx, "/api/v3/sources/saml/?"+q.Encode(), &out); err != nil {
		return nil, false, err
	}
	if len(out.Results) == 0 {
		return nil, false, nil
	}
	return &out.Results[0], true, nil
}

func (c *Client) FindLDAPSourceBySlug(ctx context.Context, slug string) (*sourceRef, bool, error) {
	q := url.Values{}
	q.Set("slug", slug)
	var out listResponse[sourceRef]
	if err := c.getJSON(ctx, "/api/v3/sources/ldap/?"+q.Encode(), &out); err != nil {
		return nil, false, err
	}
	if len(out.Results) == 0 {
		return nil, false, nil
	}
	return &out.Results[0], true, nil
}

type OAuthSourceRequest struct {
	Name                string
	Slug                string
	Enabled             bool
	ProviderType        string
	ClientID            string
	ClientSecret        string
	AuthorizationURL    string
	AccessTokenURL      string
	ProfileURL          string
	Scopes              string
	AuthenticationFlow  string
	EnrollmentFlow      string
}

func (c *Client) CreateOAuthSource(ctx context.Context, req OAuthSourceRequest) (string, error) {
	body := map[string]any{
		"name":                 req.Name,
		"slug":                 req.Slug,
		"enabled":              req.Enabled,
		"provider_type":        req.ProviderType,
		"consumer_key":         req.ClientID,
		"consumer_secret":      req.ClientSecret,
		"authorization_url":    req.AuthorizationURL,
		"access_token_url":     req.AccessTokenURL,
		"profile_url":          req.ProfileURL,
		"authentication_flow":  req.AuthenticationFlow,
		"enrollment_flow":      req.EnrollmentFlow,
		"user_matching_mode":   "email_link",
		"policy_engine_mode":   "any",
		"request_token_url":    "",
		"additional_scopes":    req.Scopes,
	}
	var created sourceRef
	if err := c.postJSON(ctx, "/api/v3/sources/oauth/", body, &created); err != nil {
		return "", err
	}
	return created.PK, nil
}

func (c *Client) UpdateOAuthSource(ctx context.Context, pk string, req OAuthSourceRequest) error {
	body := map[string]any{
		"name":                req.Name,
		"slug":                req.Slug,
		"enabled":             req.Enabled,
		"provider_type":       req.ProviderType,
		"consumer_key":        req.ClientID,
		"authorization_url":   req.AuthorizationURL,
		"access_token_url":    req.AccessTokenURL,
		"profile_url":         req.ProfileURL,
		"authentication_flow": req.AuthenticationFlow,
		"enrollment_flow":     req.EnrollmentFlow,
		"additional_scopes":   req.Scopes,
	}
	if strings.TrimSpace(req.ClientSecret) != "" {
		body["consumer_secret"] = req.ClientSecret
	}
	return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk), body)
}

func (c *Client) DeleteOAuthSource(ctx context.Context, pk string) error {
	return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/oauth/%s/", pk))
}

type SAMLSourceRequest struct {
	Name               string
	Slug               string
	Enabled            bool
	MetadataURL        string
	MetadataXML        string
	EntityID           string
	SSOURL             string
	SLOURL             string
	SigningCert        string
	AuthenticationFlow string
	EnrollmentFlow     string
}

func (c *Client) CreateSAMLSource(ctx context.Context, req SAMLSourceRequest) (string, error) {
	body := map[string]any{
		"name":                req.Name,
		"slug":                req.Slug,
		"enabled":             req.Enabled,
		"metadata_url":        req.MetadataURL,
		"sso_url":             req.SSOURL,
		"slo_url":             req.SLOURL,
		"issuer":              req.EntityID,
		"authentication_flow": req.AuthenticationFlow,
		"enrollment_flow":     req.EnrollmentFlow,
		"user_matching_mode":  "email_link",
		"policy_engine_mode":  "any",
	}
	if strings.TrimSpace(req.MetadataXML) != "" {
		body["metadata"] = req.MetadataXML
	}
	if strings.TrimSpace(req.SigningCert) != "" {
		body["verification_kp"] = req.SigningCert
	}
	var created sourceRef
	if err := c.postJSON(ctx, "/api/v3/sources/saml/", body, &created); err != nil {
		return "", err
	}
	return created.PK, nil
}

func (c *Client) UpdateSAMLSource(ctx context.Context, pk string, req SAMLSourceRequest) error {
	body := map[string]any{
		"name":                req.Name,
		"slug":                req.Slug,
		"enabled":             req.Enabled,
		"metadata_url":        req.MetadataURL,
		"sso_url":             req.SSOURL,
		"slo_url":             req.SLOURL,
		"issuer":              req.EntityID,
		"authentication_flow": req.AuthenticationFlow,
		"enrollment_flow":     req.EnrollmentFlow,
	}
	if strings.TrimSpace(req.MetadataXML) != "" {
		body["metadata"] = req.MetadataXML
	}
	if strings.TrimSpace(req.SigningCert) != "" {
		body["verification_kp"] = req.SigningCert
	}
	return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk), body)
}

func (c *Client) DeleteSAMLSource(ctx context.Context, pk string) error {
	return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/saml/%s/", pk))
}

type LDAPSourceRequest struct {
	Name               string
	Slug               string
	Enabled            bool
	ServerURI          string
	BindDN             string
	BindPassword       string
	BaseDN             string
	UserFilter         string
	StartTLS           bool
	SyncUsers          bool
	AuthenticationFlow string
	EnrollmentFlow     string
}

func (c *Client) CreateLDAPSource(ctx context.Context, req LDAPSourceRequest) (string, error) {
	body := map[string]any{
		"name":                req.Name,
		"slug":                req.Slug,
		"enabled":             req.Enabled,
		"server_uri":          req.ServerURI,
		"bind_cn":             req.BindDN,
		"bind_password":       req.BindPassword,
		"base_dn":             req.BaseDN,
		"search_mode":         "direct",
		"start_tls":           req.StartTLS,
		"sync_users":          req.SyncUsers,
		"authentication_flow": req.AuthenticationFlow,
		"enrollment_flow":     req.EnrollmentFlow,
		"user_matching_mode":  "email_link",
		"policy_engine_mode":  "any",
	}
	if filter := strings.TrimSpace(req.UserFilter); filter != "" {
		body["search_filter"] = filter
	}
	var created sourceRef
	if err := c.postJSON(ctx, "/api/v3/sources/ldap/", body, &created); err != nil {
		return "", err
	}
	return created.PK, nil
}

func (c *Client) UpdateLDAPSource(ctx context.Context, pk string, req LDAPSourceRequest) error {
	body := map[string]any{
		"name":                req.Name,
		"slug":                req.Slug,
		"enabled":             req.Enabled,
		"server_uri":          req.ServerURI,
		"bind_cn":             req.BindDN,
		"base_dn":             req.BaseDN,
		"start_tls":           req.StartTLS,
		"sync_users":          req.SyncUsers,
		"authentication_flow": req.AuthenticationFlow,
		"enrollment_flow":     req.EnrollmentFlow,
	}
	if filter := strings.TrimSpace(req.UserFilter); filter != "" {
		body["search_filter"] = filter
	}
	if strings.TrimSpace(req.BindPassword) != "" {
		body["bind_password"] = req.BindPassword
	}
	return c.patchJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk), body)
}

func (c *Client) DeleteLDAPSource(ctx context.Context, pk string) error {
	return c.deleteJSON(ctx, fmt.Sprintf("/api/v3/sources/ldap/%s/", pk))
}

func (c *Client) deleteJSON(ctx context.Context, path string) error {
	return c.doJSON(ctx, "DELETE", path, nil, nil)
}

func (c *Client) FindDefaultBrand(ctx context.Context) (*brandRef, error) {
	var out listResponse[brandRef]
	if err := c.getJSON(ctx, "/api/v3/core/brands/", &out); err != nil {
		return nil, err
	}
	for _, b := range out.Results {
		if b.Domain == "authentik-default" || strings.Contains(strings.ToLower(b.Domain), "default") {
			return &b, nil
		}
	}
	if len(out.Results) > 0 {
		return &out.Results[0], nil
	}
	return nil, fmt.Errorf("no brand found")
}

func (c *Client) SetBrandEnrollmentFlow(ctx context.Context, brandPK string, flowPK *string) error {
	body := map[string]any{}
	if flowPK == nil || strings.TrimSpace(*flowPK) == "" {
		body["flow_enrollment"] = nil
	} else {
		body["flow_enrollment"] = *flowPK
	}
	return c.patchJSON(ctx, fmt.Sprintf("/api/v3/core/brands/%s/", brandPK), body)
}

func (c *Client) FindFlowInstanceBySlug(ctx context.Context, slug string) (*flowInstance, bool, error) {
	q := url.Values{}
	q.Set("slug", slug)
	var out listResponse[flowInstance]
	if err := c.getJSON(ctx, "/api/v3/flows/instances/?"+q.Encode(), &out); err != nil {
		return nil, false, err
	}
	if len(out.Results) == 0 {
		return nil, false, nil
	}
	return &out.Results[0], true, nil
}

func (c *Client) ResolveSourceFlows(ctx context.Context) (authFlow, enrollmentFlow string, err error) {
	authFlow, err = c.FindFlowBySlug(ctx, "default-source-authentication")
	if err != nil {
		authFlow, err = c.FindFlowBySlug(ctx, "default-authentication-flow")
	}
	if err != nil {
		return "", "", err
	}
	enrollmentFlow, err = c.FindFlowBySlug(ctx, "default-source-enrollment")
	if err != nil {
		enrollmentFlow, _ = c.FindFlowBySlug(ctx, "default-enrollment-flow")
	}
	return authFlow, enrollmentFlow, nil
}
