package migration

import (
	"context"
	"net/http"
	"strings"
	"testing"
)

func TestGraphWellKnownFolder(t *testing.T) {
	remote, ftype := graphWellKnownFolder("inbox", "Inbox")
	if remote != "INBOX" || ftype != "inbox" {
		t.Fatalf("got %q / %q", remote, ftype)
	}

	remote, ftype = graphWellKnownFolder("", "Projects")
	if remote != "PROJECTS" || ftype != "custom" {
		t.Fatalf("custom folder: got %q / %q", remote, ftype)
	}
}

func TestGraphFlags(t *testing.T) {
	flags := graphFlags(true, "notFlagged")
	if len(flags) != 1 || flags[0] != "\\Seen" {
		t.Fatalf("read flags: %v", flags)
	}
	flags = graphFlags(false, "flagged")
	if len(flags) != 1 || flags[0] != "\\Flagged" {
		t.Fatalf("flagged: %v", flags)
	}
}

func TestGraphRecipientsJSON(t *testing.T) {
	raw := graphRecipientsJSON([]graphRecipient{
		{EmailAddress: graphEmailAddress{Name: "Bob", Address: "bob@example.com"}},
	})
	if string(raw) != `[{"name":"Bob","email":"bob@example.com"}]` {
		t.Fatalf("unexpected json: %s", raw)
	}
}

func TestParseGraphTime(t *testing.T) {
	tm := parseGraphTime("2024-05-01T12:34:56Z")
	if tm.IsZero() {
		t.Fatal("expected parsed time")
	}
}

func TestRemoteMessageUIDMatchesGmailUID(t *testing.T) {
	id := "abc123"
	if remoteMessageUID(id) != gmailUID(id) {
		t.Fatal("uid helpers diverged")
	}
}

func TestGraphFolderQueueSortedAndCached(t *testing.T) {
	g := NewGraphImporter(nil)
	g.folders = map[string]graphFolderMeta{
		"sent-folder": {RemoteName: "SENT", FolderType: "sent"},
		"inbox-folder": {RemoteName: "INBOX", FolderType: "inbox"},
	}
	cursor := map[string]any{}
	queue := g.folderQueue(cursor)
	if len(queue) != 2 {
		t.Fatalf("queue len = %d", len(queue))
	}
	if queue[0] != "inbox-folder" || queue[1] != "sent-folder" {
		t.Fatalf("queue order = %v", queue)
	}
	cached := readGraphFolderQueue(cursor)
	if len(cached) != 2 || cached[0] != "inbox-folder" {
		t.Fatalf("cached queue = %v", cached)
	}
}

func TestGraphFolderMessagesURLUsesMailFoldersPath(t *testing.T) {
	g := NewGraphImporter(nil).WithBaseURL("https://graph.test")
	listURL := g.folderMessagesURL("folder-abc")
	if !strings.Contains(listURL, "/mailFolders/folder-abc/messages") {
		t.Fatalf("url = %q", listURL)
	}
	if strings.Contains(listURL, "/me/messages") {
		t.Fatalf("flat messages path should not be used: %q", listURL)
	}
}

func TestGraphEnsureFoldersPaginates(t *testing.T) {
	pages := 0
	client := mockGraphHTTPClient(t, func(w http.ResponseWriter, r *http.Request) {
		if strings.Contains(r.URL.Path, "/childFolders") {
			_, _ = w.Write([]byte(`{"value":[]}`))
			return
		}
		if !strings.HasSuffix(r.URL.Path, "/mailFolders") {
			http.NotFound(w, r)
			return
		}
		pages++
		if pages == 1 {
			_, _ = w.Write([]byte(`{
				"value":[{"id":"inbox-id","displayName":"Inbox","wellKnownName":"inbox"}],
				"@odata.nextLink":"https://graph.microsoft.com/v1.0/me/mailFolders?$top=100&$skip=100"
			}`))
			return
		}
		_, _ = w.Write([]byte(`{"value":[{"id":"sent-id","displayName":"Sent","wellKnownName":"sentitems"}]}`))
	})

	g := NewGraphImporter(nil).WithHTTPClient(client)
	if err := g.ensureGraphFolders(context.Background(), "token"); err != nil {
		t.Fatalf("ensure folders: %v", err)
	}
	if pages != 2 {
		t.Fatalf("pages = %d, want 2", pages)
	}
	if len(g.folders) != 2 {
		t.Fatalf("folders = %d", len(g.folders))
	}
}

func TestGraphEnsureFoldersDiscoversNestedChildFolders(t *testing.T) {
	childCalls := 0
	client := mockGraphHTTPClient(t, func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasSuffix(r.URL.Path, "/mailFolders"):
			_, _ = w.Write([]byte(`{"value":[
				{"id":"inbox-id","displayName":"Inbox","wellKnownName":"inbox"}
			]}`))
		case strings.Contains(r.URL.Path, "/mailFolders/inbox-id/childFolders"):
			childCalls++
			_, _ = w.Write([]byte(`{"value":[
				{"id":"projects-id","displayName":"Projects","wellKnownName":""}
			]}`))
		case strings.Contains(r.URL.Path, "/mailFolders/projects-id/childFolders"):
			childCalls++
			_, _ = w.Write([]byte(`{"value":[
				{"id":"nested-id","displayName":"2024","wellKnownName":""}
			]}`))
		case strings.Contains(r.URL.Path, "/mailFolders/nested-id/childFolders"):
			childCalls++
			_, _ = w.Write([]byte(`{"value":[]}`))
		default:
			http.NotFound(w, r)
		}
	})

	g := NewGraphImporter(nil).WithHTTPClient(client)
	if err := g.ensureGraphFolders(context.Background(), "token"); err != nil {
		t.Fatalf("ensure folders: %v", err)
	}
	if childCalls != 3 {
		t.Fatalf("childFolders calls = %d, want 3", childCalls)
	}
	if len(g.folders) != 3 {
		t.Fatalf("folders = %d, want 3", len(g.folders))
	}
	projects := g.folders["projects-id"]
	if projects.RemoteName != "INBOX/PROJECTS" || projects.FolderType != "custom" {
		t.Fatalf("projects meta = %+v", projects)
	}
	nested := g.folders["nested-id"]
	if nested.RemoteName != "INBOX/PROJECTS/2024" || nested.FolderType != "custom" {
		t.Fatalf("nested meta = %+v", nested)
	}
}

func TestGraphEnsureFoldersChildFoldersPaginate(t *testing.T) {
	pages := 0
	client := mockGraphHTTPClient(t, func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasSuffix(r.URL.Path, "/mailFolders"):
			_, _ = w.Write([]byte(`{"value":[
				{"id":"inbox-id","displayName":"Inbox","wellKnownName":"inbox"}
			]}`))
		case strings.Contains(r.URL.Path, "/mailFolders/inbox-id/childFolders"):
			pages++
			if pages == 1 {
				_, _ = w.Write([]byte(`{
					"value":[{"id":"child-a","displayName":"Alpha","wellKnownName":""}],
					"@odata.nextLink":"https://graph.microsoft.com/v1.0/me/mailFolders/inbox-id/childFolders?$skip=100"
				}`))
				return
			}
			_, _ = w.Write([]byte(`{"value":[{"id":"child-b","displayName":"Beta","wellKnownName":""}]}`))
		case strings.Contains(r.URL.Path, "/childFolders"):
			_, _ = w.Write([]byte(`{"value":[]}`))
		default:
			http.NotFound(w, r)
		}
	})

	g := NewGraphImporter(nil).WithHTTPClient(client)
	if err := g.ensureGraphFolders(context.Background(), "token"); err != nil {
		t.Fatalf("ensure folders: %v", err)
	}
	if pages != 2 {
		t.Fatalf("child pages = %d, want 2", pages)
	}
	if g.folders["child-a"].RemoteName != "INBOX/ALPHA" {
		t.Fatalf("child-a remote = %q", g.folders["child-a"].RemoteName)
	}
	if g.folders["child-b"].RemoteName != "INBOX/BETA" {
		t.Fatalf("child-b remote = %q", g.folders["child-b"].RemoteName)
	}
}

func TestGraphNestedFolderMeta(t *testing.T) {
	remote, ftype := graphNestedFolderMeta("INBOX", "", "My Folder")
	if remote != "INBOX/MY_FOLDER" || ftype != "custom" {
		t.Fatalf("got %q / %q", remote, ftype)
	}
	remote, ftype = graphNestedFolderMeta("", "", "Top")
	if remote != "TOP" || ftype != "custom" {
		t.Fatalf("top-level custom: got %q / %q", remote, ftype)
	}
}

func TestGraphFolderQueueIncludesNestedFolders(t *testing.T) {
	g := NewGraphImporter(nil)
	g.folders = map[string]graphFolderMeta{
		"inbox-id":     {RemoteName: "INBOX", FolderType: "inbox"},
		"projects-id":  {RemoteName: "INBOX/PROJECTS", FolderType: "custom"},
		"archive-id":   {RemoteName: "ARCHIVE", FolderType: "archive"},
	}
	cursor := map[string]any{}
	queue := g.folderQueue(cursor)
	if len(queue) != 3 {
		t.Fatalf("queue len = %d", len(queue))
	}
}

func TestGraphFolderQueueMergesLegacyCursor(t *testing.T) {
	g := NewGraphImporter(nil)
	g.folders = map[string]graphFolderMeta{
		"inbox-id":    {RemoteName: "INBOX", FolderType: "inbox"},
		"sent-id":     {RemoteName: "SENT", FolderType: "sent"},
		"projects-id": {RemoteName: "INBOX/PROJECTS", FolderType: "custom", ParentGraphID: "inbox-id"},
	}
	cursor := map[string]any{
		"graphFolderQueue": []any{"inbox-id", "sent-id"},
		"folderIndex":      float64(2),
	}
	queue := g.folderQueue(cursor)
	if len(queue) != 3 {
		t.Fatalf("queue len = %d, want 3", len(queue))
	}
	if queue[0] != "inbox-id" || queue[1] != "sent-id" || queue[2] != "projects-id" {
		t.Fatalf("queue order = %v", queue)
	}
}

func TestMergeGraphFolderQueuePreservesOrder(t *testing.T) {
	cursor := map[string]any{
		"graphFolderQueue": []any{"b-folder", "a-folder"},
	}
	merged := mergeGraphFolderQueue(cursor, []string{"a-folder", "b-folder", "c-folder"})
	if len(merged) != 3 {
		t.Fatalf("merged len = %d", len(merged))
	}
	if merged[0] != "b-folder" || merged[1] != "a-folder" || merged[2] != "c-folder" {
		t.Fatalf("merged order = %v", merged)
	}
}

func TestMergeGraphFolderQueueNoOpWhenComplete(t *testing.T) {
	cursor := map[string]any{
		"graphFolderQueue": []any{"inbox-id", "sent-id"},
	}
	merged := mergeGraphFolderQueue(cursor, []string{"inbox-id", "sent-id"})
	if len(merged) != 2 {
		t.Fatalf("merged len = %d", len(merged))
	}
	if merged[0] != "inbox-id" || merged[1] != "sent-id" {
		t.Fatalf("merged = %v", merged)
	}
}

func TestGraphInitFolderDeltaLink(t *testing.T) {
	client := mockGraphHTTPClient(t, func(w http.ResponseWriter, r *http.Request) {
		if strings.Contains(r.URL.Path, "/mailFolders/inbox-id/messages/delta") {
			_, _ = w.Write([]byte(`{"@odata.deltaLink":"https://graph.microsoft.com/v1.0/me/mailFolders/inbox-id/messages/delta?token=done"}`))
			return
		}
		http.NotFound(w, r)
	})

	g := NewGraphImporter(nil).WithHTTPClient(client)
	link, err := g.initFolderDeltaLink(context.Background(), "token", "inbox-id")
	if err != nil {
		t.Fatalf("init delta: %v", err)
	}
	if !strings.Contains(link, "/mailFolders/inbox-id/messages/delta") {
		t.Fatalf("delta link = %q", link)
	}
}

func TestGraphFolderDeltaLinkHelpers(t *testing.T) {
	cursor := map[string]any{}
	setGraphFolderDeltaLink(cursor, "inbox-id", "https://delta/inbox")
	links := graphFolderDeltaLinks(cursor)
	if links["inbox-id"] != "https://delta/inbox" {
		t.Fatalf("links = %v", links)
	}
}
