package platform

import (
	"context"
	"encoding/json"
	"log/slog"
	"net/http"
	"strings"
	"time"
)

// HealIdentity refreshes a persisted session's display identity (name + avatar) from the
// authoritative /api/me and re-persists it, BEFORE the session is injected into the WebView.
//
// Why: a session minted by an older build derived identity from the broker's nameless machine
// token, baking the subject UUID (and no avatar) into the on-disk session. The newer login path
// records the real name/picture, but an already-logged-in user keeps the stale session. Correcting
// it only in the WebView (per launch, via /api/me) is fragile — when the boot access token has
// expired and the refresh is momentarily flaky, the correction silently fails and the UUID shows
// again (the reported intermittent regression). Healing the persisted session here fixes it at the
// source: once a single launch succeeds, the on-disk identity is correct and every later boot is
// correct without any /api/me dependency.
//
// Synchronous and bounded so it can run before wails.Run; best-effort — on any network failure it
// returns the session unchanged (offline simply keeps whatever was last persisted, which a prior
// successful heal already corrected). If the access token has expired it refreshes once (and the
// rotated refresh token is persisted so it is not lost).
func HealIdentity(s *LoginResult, apiBase string) *LoginResult {
	if s == nil || s.AccessToken == "" {
		return s
	}
	// Once healed, the persisted name is a real display name (not the subject UUID); skip the
	// /api/me round-trip then so it costs nothing on every later launch. A stale session (name
	// still equal to the UUID) falls through and heals — fetching the avatar in the same pass.
	if s.User.Name != "" && s.User.Name != s.User.ID {
		return s
	}
	ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
	defer cancel()

	info, status := fetchMe(ctx, apiBase, s.AccessToken)

	tokenRotated := false
	if status == http.StatusUnauthorized && s.RefreshToken != "" {
		if cfg, err := FetchOAuthConfig(ctx, apiBase); err == nil {
			if tok, rerr := NewFlow(cfg, func(string) {}).Refresh(ctx, s.RefreshToken); rerr == nil && tok.AccessToken != "" {
				s.AccessToken = tok.AccessToken
				if tok.RefreshToken != "" {
					s.RefreshToken = tok.RefreshToken // broker rotates refresh; the old one is now dead
				}
				s.ExpiresIn = tok.ExpiresIn
				tokenRotated = true
				info, status = fetchMe(ctx, apiBase, s.AccessToken)
			}
		}
	}

	changed := tokenRotated // a rotated refresh token must be persisted, regardless of the heal outcome
	if status == http.StatusOK && info.ID != "" {
		// Replace a stale name only with a real display name (not the subject UUID).
		if info.Name != "" && info.Name != info.ID && info.Name != s.User.Name {
			s.User.Name = info.Name
			changed = true
		}
		if info.Avatar != "" && info.Avatar != s.User.Avatar {
			s.User.Avatar = info.Avatar
			changed = true
		}
	}
	if changed {
		if err := SaveSession(*s); err != nil {
			slog.Warn("cdrop: heal identity save failed", "err", err)
		}
	}
	return s
}

// fetchMe GETs /api/me with the access token, returning the identity and HTTP status (0 on a
// transport error). It hits the public origin directly (no WebView proxy needed here).
func fetchMe(ctx context.Context, apiBase, accessToken string) (UserInfo, int) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(apiBase, "/")+"/api/me", nil)
	if err != nil {
		return UserInfo{}, 0
	}
	req.Header.Set("Authorization", "Bearer "+accessToken)
	req.Header.Set("Accept", "application/json")
	resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
	if err != nil {
		return UserInfo{}, 0
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return UserInfo{}, resp.StatusCode
	}
	var m struct {
		UserID string `json:"user_id"`
		Name   string `json:"name"`
		Avatar string `json:"avatar"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return UserInfo{}, 0
	}
	return UserInfo{ID: m.UserID, Name: m.Name, Avatar: m.Avatar}, http.StatusOK
}
