package migration

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/ultisuite/ulti-backend/internal/mail/credentials"
)

type progressUpdater func(status string, cursor, stats map[string]any, jobErr string) error

type migrationUser struct {
	Email      string
	ExternalID string
	Name       string
}

func resolveMigrationUser(ctx context.Context, db *pgxpool.Pool, userID string) (migrationUser, error) {
	var u migrationUser
	err := db.QueryRow(ctx, `
		SELECT COALESCE(email, ''), COALESCE(external_id, ''), COALESCE(name, '')
		FROM users WHERE id = $1::uuid
	`, userID).Scan(&u.Email, &u.ExternalID, &u.Name)
	if err != nil {
		return migrationUser{}, fmt.Errorf("migration user not found")
	}
	if u.Email == "" {
		return migrationUser{}, fmt.Errorf("migration user email missing")
	}
	return u, nil
}

func migrationHTTPClient() *http.Client {
	return &http.Client{Timeout: 90 * time.Second}
}

func apiGet(ctx context.Context, client *http.Client, url, accessToken string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+accessToken)
	resp, err := migrationDo(ctx, client, req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

func alreadyImported(store *ImportedItemStore, id string) bool {
	if store == nil {
		return false
	}
	return store.Has(id)
}

func calendarSyncTokens(cursor map[string]any) map[string]string {
	raw, _ := cursor["calendarSyncTokens"].(map[string]any)
	out := make(map[string]string, len(raw))
	for k, v := range raw {
		if s, ok := v.(string); ok && s != "" {
			out[k] = s
		}
	}
	return out
}

func setCalendarSyncToken(cursor map[string]any, calID, token string) {
	if calID == "" || token == "" {
		return
	}
	raw, _ := cursor["calendarSyncTokens"].(map[string]any)
	if raw == nil {
		raw = map[string]any{}
		cursor["calendarSyncTokens"] = raw
	}
	raw[calID] = token
}

func calendarDeltaLinks(cursor map[string]any) map[string]string {
	raw, _ := cursor["calendarDeltaLinks"].(map[string]any)
	out := make(map[string]string, len(raw))
	for k, v := range raw {
		if s, ok := v.(string); ok && s != "" {
			out[k] = s
		}
	}
	return out
}

func setCalendarDeltaLink(cursor map[string]any, calID, link string) {
	if calID == "" || link == "" {
		return
	}
	raw, _ := cursor["calendarDeltaLinks"].(map[string]any)
	if raw == nil {
		raw = map[string]any{}
		cursor["calendarDeltaLinks"] = raw
	}
	raw[calID] = link
}

func graphFolderDeltaLinks(cursor map[string]any) map[string]string {
	raw, _ := cursor["folderDeltaLinks"].(map[string]any)
	out := make(map[string]string, len(raw))
	for k, v := range raw {
		if s, ok := v.(string); ok && s != "" {
			out[k] = s
		}
	}
	return out
}

func setGraphFolderDeltaLink(cursor map[string]any, folderID, link string) {
	if folderID == "" || link == "" {
		return
	}
	raw, _ := cursor["folderDeltaLinks"].(map[string]any)
	if raw == nil {
		raw = map[string]any{}
		cursor["folderDeltaLinks"] = raw
	}
	raw[folderID] = link
}

func readGraphFolderQueue(cursor map[string]any) []string {
	raw, _ := cursor["graphFolderQueue"].([]any)
	out := make([]string, 0, len(raw))
	for _, v := range raw {
		if s, ok := v.(string); ok && s != "" {
			out = append(out, s)
		}
	}
	return out
}

func writeGraphFolderQueue(cursor map[string]any, ids []string) {
	queue := make([]any, 0, len(ids))
	for _, id := range ids {
		if id != "" {
			queue = append(queue, id)
		}
	}
	cursor["graphFolderQueue"] = queue
}

// mergeGraphFolderQueue extends a cached import queue with newly discovered folder
// IDs while preserving order for folders already in progress.
func mergeGraphFolderQueue(cursor map[string]any, discovered []string) []string {
	existing := readGraphFolderQueue(cursor)
	if len(existing) == 0 {
		writeGraphFolderQueue(cursor, discovered)
		return discovered
	}
	seen := make(map[string]struct{}, len(existing))
	for _, id := range existing {
		seen[id] = struct{}{}
	}
	merged := make([]string, len(existing), len(existing)+len(discovered))
	copy(merged, existing)
	for _, id := range discovered {
		if id == "" {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		merged = append(merged, id)
		seen[id] = struct{}{}
	}
	if len(merged) != len(existing) {
		writeGraphFolderQueue(cursor, merged)
	}
	return merged
}

func migrationContactPath(bookPath, provider, sourceID string) string {
	uid := sanitizeMigrationUID(provider, sourceID)
	return bookPath + uid + ".vcf"
}

func migrationEventPath(calPath, provider, sourceID string) string {
	uid := sanitizeMigrationUID(provider, sourceID)
	return calPath + uid + ".ics"
}

func sanitizeMigrationUID(provider, sourceID string) string {
	sourceID = strings.TrimSpace(sourceID)
	sourceID = strings.ReplaceAll(sourceID, "/", "-")
	return provider + "-" + sourceID + "@ultimail.migrated"
}

func applyOAuthToken(cred credentials.Credential, token *oauthToken) credentials.Credential {
	cred.AuthType = credentials.AuthOAuth2
	cred.AccessToken = token.AccessToken
	if token.RefreshToken != "" {
		cred.RefreshToken = token.RefreshToken
	}
	if !token.Expiry.IsZero() {
		cred.Expiry = token.Expiry.UTC()
	}
	return cred
}

type oauthToken struct {
	AccessToken  string
	RefreshToken string
	Expiry       time.Time
}
