package zitadel

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// verifyPasswordSession checks credentials via ZITADEL v2 Sessions API (PAT-backed).
// Used when OAuth resource-owner password grant is unavailable (default in ZITADEL v2).
func (c *Client) verifyPasswordSession(ctx context.Context, loginName, password string) (*TokenResult, error) {
	if c.conf.ServiceUserToken == "" {
		return nil, fmt.Errorf("zitadel: service user token is required for session password verification")
	}
	loginName = strings.TrimSpace(loginName)
	if loginName == "" || password == "" {
		return nil, ErrInvalidCredentials
	}

	var created struct {
		SessionID string `json:"sessionId"`
	}
	if err := c.doSessionJSON(ctx, http.MethodPost, c.apiBase+"/v2/sessions", map[string]any{
		"checks": map[string]any{
			"user": map[string]any{"loginName": loginName},
		},
	}, &created); err != nil {
		if isSessionUserNotFound(err) {
			return nil, ErrInvalidCredentials
		}
		return nil, err
	}
	if created.SessionID == "" {
		return nil, fmt.Errorf("zitadel: create session: empty session id")
	}

	if err := c.doSessionJSON(ctx, http.MethodPatch, c.apiBase+"/v2/sessions/"+created.SessionID, map[string]any{
		"checks": map[string]any{
			fieldPassword: map[string]any{fieldPassword: password},
		},
	}, nil); err != nil {
		if isSessionPasswordInvalid(err) {
			return nil, ErrInvalidCredentials
		}
		return nil, err
	}

	var got struct {
		Session struct {
			Factors struct {
				User struct {
					ID        string `json:"id"`
					LoginName string `json:"loginName"`
				} `json:"user"`
				Password struct {
					VerifiedAt string `json:"verifiedAt"`
				} `json:"password"`
			} `json:"factors"`
		} `json:"session"`
	}
	if err := c.doSessionJSON(ctx, http.MethodGet, c.apiBase+"/v2/sessions/"+created.SessionID, nil, &got); err != nil {
		return nil, err
	}
	if got.Session.Factors.Password.VerifiedAt == "" || got.Session.Factors.User.ID == "" {
		return nil, ErrInvalidCredentials
	}
	return &TokenResult{
		Subject: got.Session.Factors.User.ID,
		Email:   got.Session.Factors.User.LoginName,
	}, nil
}

func (c *Client) doSessionJSON(ctx context.Context, method, endpoint string, body, out any) error {
	var r io.Reader
	if body != nil {
		raw, err := json.Marshal(body)
		if err != nil {
			return fmt.Errorf("zitadel: marshal request: %w", err)
		}
		r = bytes.NewReader(raw)
	}
	req, err := http.NewRequestWithContext(ctx, method, endpoint, r)
	if err != nil {
		return fmt.Errorf("zitadel: new request: %w", err)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", c.serviceAuth())

	resp, err := c.http.Do(req)
	if err != nil {
		return fmt.Errorf("zitadel: %s %s: %w", method, endpoint, err)
	}
	defer resp.Body.Close()

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("zitadel: read response body: %w", err)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("zitadel: %s %s: status %d: %s", method, endpoint, resp.StatusCode, truncateBody(raw))
	}
	if out != nil && len(raw) > 0 {
		if err := json.Unmarshal(raw, out); err != nil {
			return fmt.Errorf("zitadel: decode response: %w", err)
		}
	}
	return nil
}

func isSessionUserNotFound(err error) bool {
	return err != nil && strings.Contains(err.Error(), "status 404")
}

func isSessionPasswordInvalid(err error) bool {
	if err == nil {
		return false
	}
	s := err.Error()
	return strings.Contains(s, "status 400") && strings.Contains(s, "Password is invalid")
}
