package feishu

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const feishuAuthStateTTL = 10 * time.Minute
const feishuAuthRequiredScopeUserID = "contact:user.employee_id:readonly"
const feishuAuthAuthorizePath = "/open-apis/authen/v1/authorize"
const feishuAuthTokenPath = "/open-apis/authen/v2/oauth/token"

type FeishuAuthHTTPClientOptions struct {
	BaseURL    string
	AppID      string
	AppSecret  string
	HTTPClient *http.Client
}

type FeishuAuthIdentity struct {
	UserID    string
	OpenID    string
	UnionID   string
	Name      string
	Email     string
	AvatarURL string
	TenantKey string
}

type FeishuAuthHTTPClient struct {
	apiBaseURL       string
	authorizeBaseURL string
	appID            string
	appSecret        string
	httpClient       *http.Client
}

func NewFeishuAuthHTTPClient(options FeishuAuthHTTPClientOptions) (*FeishuAuthHTTPClient, error) {
	appID := strings.TrimSpace(options.AppID)
	if appID == "" {
		return nil, storeerr.BadRequest("feishu app id is required")
	}
	appSecret := strings.TrimSpace(options.AppSecret)
	if appSecret == "" {
		return nil, storeerr.BadRequest("feishu app secret is required")
	}
	baseURL := strings.TrimSpace(options.BaseURL)
	if baseURL == "" {
		baseURL = DefaultBaseURL
	}
	parsedBaseURL, err := url.Parse(baseURL)
	if err != nil {
		return nil, storeerr.BadRequest("feishu base url is invalid")
	}
	switch strings.ToLower(parsedBaseURL.Scheme) {
	case "http", "https":
	default:
		return nil, storeerr.BadRequest("feishu base url must use http or https")
	}
	httpClient := options.HTTPClient
	if httpClient == nil {
		httpClient = &http.Client{Timeout: 10 * time.Second}
	}
	return &FeishuAuthHTTPClient{
		apiBaseURL:       strings.TrimRight(baseURL, "/"),
		authorizeBaseURL: resolveFeishuAuthorizeBaseURL(parsedBaseURL),
		appID:            appID,
		appSecret:        appSecret,
		httpClient:       httpClient,
	}, nil
}

func (client *FeishuAuthHTTPClient) BuildAuthorizeURL(redirectURI string, state string) (string, error) {
	redirectTarget, err := normalizeFeishuRedirectURI(redirectURI)
	if err != nil {
		return "", err
	}
	trimmedState := strings.TrimSpace(state)
	if trimmedState == "" {
		return "", storeerr.BadRequest("feishu auth state is required")
	}

	query := url.Values{}
	query.Set("client_id", client.appID)
	query.Set("response_type", "code")
	query.Set("redirect_uri", redirectTarget)
	query.Set("state", trimmedState)
	query.Set("scope", feishuAuthRequiredScopeUserID)
	return client.authorizeBaseURL + feishuAuthAuthorizePath + "?" + query.Encode(), nil
}

func (client *FeishuAuthHTTPClient) ExchangeCode(ctx context.Context, code string, redirectURI string) (FeishuAuthIdentity, error) {
	trimmedCode := strings.TrimSpace(code)
	if trimmedCode == "" {
		return FeishuAuthIdentity{}, storeerr.BadRequest("feishu authorization code is required")
	}
	redirectTarget, err := normalizeFeishuRedirectURI(redirectURI)
	if err != nil {
		return FeishuAuthIdentity{}, err
	}

	userAccessToken, err := client.exchangeUserAccessToken(ctx, trimmedCode, redirectTarget)
	if err != nil {
		return FeishuAuthIdentity{}, err
	}
	return client.loadUserInfo(ctx, userAccessToken)
}

func (client *FeishuAuthHTTPClient) exchangeUserAccessToken(ctx context.Context, code string, redirectURI string) (string, error) {
	body, err := client.doJSONRequest(ctx, http.MethodPost, feishuAuthTokenPath, map[string]string{
		"grant_type":    "authorization_code",
		"client_id":     client.appID,
		"client_secret": client.appSecret,
		"code":          code,
		"redirect_uri":  redirectURI,
	}, "")
	if err != nil {
		return "", err
	}

	var payload feishuAuthTokenResponse
	if err := model.UnmarshalJSONObject("feishu auth access token response", body, &payload); err != nil {
		return "", fmt.Errorf("decode feishu auth access token response: %w", err)
	}
	if payload.Code != 0 {
		return "", storeerr.BadRequest(feishuAuthTokenErrorMessage(payload))
	}

	accessToken := strings.TrimSpace(payload.Access_token)
	if accessToken == "" {
		return "", fmt.Errorf("feishu auth access token response missing access token")
	}
	return accessToken, nil
}

func (client *FeishuAuthHTTPClient) loadUserInfo(ctx context.Context, accessToken string) (FeishuAuthIdentity, error) {
	body, err := client.doJSONRequest(ctx, http.MethodGet, "/open-apis/authen/v1/user_info", nil, "Bearer "+strings.TrimSpace(accessToken))
	if err != nil {
		return FeishuAuthIdentity{}, err
	}

	var payload feishuAPIResponse[feishuAuthUserInfoData]
	if err := model.UnmarshalJSONObject("feishu auth user info response", body, &payload); err != nil {
		return FeishuAuthIdentity{}, fmt.Errorf("decode feishu auth user info response: %w", err)
	}
	if payload.Code != 0 {
		return FeishuAuthIdentity{}, storeerr.BadRequest(APIErrorMessage(payload.Code, payload.Msg))
	}

	identity := payload.Data.identity()
	if identity.UserID == "" && identity.OpenID == "" && identity.UnionID == "" {
		return FeishuAuthIdentity{}, fmt.Errorf("feishu auth user info response missing identity")
	}
	return identity, nil
}

func (client *FeishuAuthHTTPClient) doJSONRequest(ctx context.Context, method string, requestPath string, requestBody any, authorization string) ([]byte, error) {
	requestURL := client.apiBaseURL + requestPath

	var bodyReader io.Reader
	if requestBody != nil {
		encodedBody, err := model.MarshalJSONObject("feishu auth request", requestBody)
		if err != nil {
			return nil, fmt.Errorf("encode feishu auth request: %w", err)
		}
		bodyReader = bytes.NewReader(encodedBody)
	}

	request, err := http.NewRequestWithContext(ctx, method, requestURL, bodyReader)
	if err != nil {
		return nil, fmt.Errorf("build feishu auth request: %w", err)
	}
	request.Header.Set("Accept", "application/json")
	if requestBody != nil {
		request.Header.Set("Content-Type", "application/json; charset=utf-8")
	}
	if strings.TrimSpace(authorization) != "" {
		request.Header.Set("Authorization", authorization)
	}

	response, err := client.httpClient.Do(request)
	if err != nil {
		return nil, fmt.Errorf("request feishu auth api: %w", err)
	}
	defer func() { _ = response.Body.Close() }()

	body, err := io.ReadAll(io.LimitReader(response.Body, 4<<20))
	if err != nil {
		return nil, fmt.Errorf("read feishu auth response: %w", err)
	}
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		return nil, storeerr.BadRequest(fmt.Sprintf("feishu request failed (%d): %s", response.StatusCode, feishuHTTPErrorMessage(body)))
	}
	return body, nil
}

type feishuAuthTokenResponse struct {
	Code              int
	Msg               string
	Error             string
	Error_description string
	Access_token      string
}

type feishuAuthUserInfoData struct {
	User_id    string
	Open_id    string
	Union_id   string
	Name       string
	En_name    string
	Email      string
	Avatar_url string
	Tenant_key string
}

func (data feishuAuthUserInfoData) identity() FeishuAuthIdentity {
	name := strings.TrimSpace(data.Name)
	if name == "" {
		name = strings.TrimSpace(data.En_name)
	}
	return FeishuAuthIdentity{
		UserID:    strings.TrimSpace(data.User_id),
		OpenID:    strings.TrimSpace(data.Open_id),
		UnionID:   strings.TrimSpace(data.Union_id),
		Name:      name,
		Email:     strings.TrimSpace(data.Email),
		AvatarURL: strings.TrimSpace(data.Avatar_url),
		TenantKey: strings.TrimSpace(data.Tenant_key),
	}
}

type AuthStatePayload struct {
	OrganizationSlug string
	IssuedAtUnix     int64
}

func EncodeAuthState(secret []byte, organizationSlug string, issuedAt time.Time) (string, error) {
	if len(secret) == 0 {
		return "", storeerr.BadRequest("feishu auth state signing secret is required")
	}
	trimmedSlug := strings.TrimSpace(organizationSlug)
	if trimmedSlug == "" {
		return "", storeerr.BadRequest("organization slug is required")
	}

	payload, err := model.MarshalJSONObject("feishu auth state payload", AuthStatePayload{
		OrganizationSlug: trimmedSlug,
		IssuedAtUnix:     issuedAt.UTC().Unix(),
	})
	if err != nil {
		return "", fmt.Errorf("encode feishu auth state payload: %w", err)
	}

	payloadToken := base64.RawURLEncoding.EncodeToString(payload)
	signature := signFeishuAuthState(secret, payloadToken)
	return payloadToken + "." + signature, nil
}

func DecodeAuthState(secret []byte, state string, now time.Time) (AuthStatePayload, error) {
	if len(secret) == 0 {
		return AuthStatePayload{}, storeerr.BadRequest("feishu auth state signing secret is required")
	}
	trimmedState := strings.TrimSpace(state)
	if trimmedState == "" {
		return AuthStatePayload{}, storeerr.BadRequest("feishu auth state is required")
	}

	parts := strings.Split(trimmedState, ".")
	if len(parts) != 2 {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}
	payloadToken := strings.TrimSpace(parts[0])
	signatureToken := strings.TrimSpace(parts[1])
	if payloadToken == "" || signatureToken == "" {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}

	expectedSignature := signFeishuAuthState(secret, payloadToken)
	if subtle.ConstantTimeCompare([]byte(expectedSignature), []byte(signatureToken)) != 1 {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}

	payloadBytes, err := base64.RawURLEncoding.DecodeString(payloadToken)
	if err != nil {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}

	var payload AuthStatePayload
	if err := model.UnmarshalJSONObject("feishu auth state payload", payloadBytes, &payload); err != nil {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}
	if strings.TrimSpace(payload.OrganizationSlug) == "" || payload.IssuedAtUnix <= 0 {
		return AuthStatePayload{}, storeerr.BadRequest("invalid feishu auth state")
	}

	issuedAt := time.Unix(payload.IssuedAtUnix, 0).UTC()
	if now.UTC().Before(issuedAt.Add(-time.Minute)) || now.UTC().After(issuedAt.Add(feishuAuthStateTTL)) {
		return AuthStatePayload{}, storeerr.BadRequest("feishu auth state expired")
	}
	return payload, nil
}

func signFeishuAuthState(secret []byte, payloadToken string) string {
	mac := hmac.New(sha256.New, secret)
	_, _ = mac.Write([]byte(payloadToken))
	return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}

func normalizeFeishuRedirectURI(redirectURI string) (string, error) {
	trimmedRedirectURI := strings.TrimSpace(redirectURI)
	if trimmedRedirectURI == "" {
		return "", storeerr.BadRequest("feishu redirect uri is required")
	}

	parsedRedirectURI, err := url.Parse(trimmedRedirectURI)
	if err != nil || parsedRedirectURI.Scheme == "" || parsedRedirectURI.Host == "" {
		return "", storeerr.BadRequest("feishu redirect uri is invalid")
	}
	switch strings.ToLower(parsedRedirectURI.Scheme) {
	case "http", "https":
	default:
		return "", storeerr.BadRequest("feishu redirect uri must use http or https")
	}
	return parsedRedirectURI.String(), nil
}

func resolveFeishuAuthorizeBaseURL(apiBaseURL *url.URL) string {
	if apiBaseURL == nil {
		return "https://accounts.feishu.cn"
	}

	host := strings.TrimSpace(apiBaseURL.Host)
	if strings.HasPrefix(host, "open.") {
		host = "accounts." + strings.TrimPrefix(host, "open.")
	}
	if host == "" {
		host = apiBaseURL.Host
	}

	authorizeBaseURL := &url.URL{
		Scheme: apiBaseURL.Scheme,
		Host:   host,
	}
	return strings.TrimRight(authorizeBaseURL.String(), "/")
}

func feishuAuthTokenErrorMessage(payload feishuAuthTokenResponse) string {
	message := strings.TrimSpace(payload.Error_description)
	if message == "" {
		message = strings.TrimSpace(payload.Msg)
	}
	if message == "" {
		message = strings.TrimSpace(payload.Error)
	}
	return APIErrorMessage(payload.Code, message)
}
