//go:build test

package photos

import (
	"encoding/json"
	"testing"
)

func TestParseAlbumsJSON(t *testing.T) {
	tests := []struct {
		name    string
		json    string
		want    []Album
		wantErr bool
	}{
		{
			name: "empty albums",
			json: `{"albums":[]}`,
			want: []Album{},
		},
		{
			name: "single album",
			json: `{"albums":[{"id":"abc","title":"Vacation"}]}`,
			want: []Album{{ID: "abc", Title: "Vacation"}},
		},
		{
			name: "multiple albums",
			json: `{"albums":[{"id":"a1","title":"Album1"},{"id":"a2","title":"Album2"}]}`,
			want: []Album{{ID: "a1", Title: "Album1"}, {ID: "a2", Title: "Album2"}},
		},
		{
			name:    "invalid json",
			json:    `not json`,
			wantErr: true,
		},
		{
			name:    "missing albums key",
			json:    `{}`,
			want:    []Album{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseAlbumsJSON(tt.json)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseAlbumsJSON() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.wantErr {
				return
			}
			if len(got) != len(tt.want) {
				t.Errorf("ParseAlbumsJSON() got %d albums, want %d", len(got), len(tt.want))
				return
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("ParseAlbumsJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestParseAssetsJSON(t *testing.T) {
	tests := []struct {
		name     string
		json     string
		want     []Asset
		wantTotal int
		wantErr  bool
		errMsg   string
	}{
		{
			name: "empty assets",
			json: `{"assets":[],"total":0}`,
			want: []Asset{},
			wantTotal: 0,
		},
		{
			name: "single asset",
			json: `{"assets":[{"id":"asset1","filename":"IMG_0001.JPG"}],"total":1}`,
			want: []Asset{{ID: "asset1", Filename: "IMG_0001.JPG"}},
			wantTotal: 1,
		},
		{
			name: "multiple assets",
			json: `{"assets":[{"id":"a1","filename":"a.jpg"},{"id":"a2","filename":"b.jpg"},{"id":"a3","filename":"c.jpg"}],"total":3}`,
			want: []Asset{{ID: "a1", Filename: "a.jpg"}, {ID: "a2", Filename: "b.jpg"}, {ID: "a3", Filename: "c.jpg"}},
			wantTotal: 3,
		},
		{
			name:    "error response",
			json:    `{"error":"album not found"}`,
			wantErr: true,
			errMsg:  "album not found",
		},
		{
			name:    "invalid json",
			json:    `not json`,
			wantErr: true,
		},
		{
			name: "empty error is not an error",
			json: `{"error":"","assets":[{"id":"a1","filename":"IMG.JPG"}],"total":1}`,
			want: []Asset{{ID: "a1", Filename: "IMG.JPG"}},
			wantTotal: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, total, err := ParseAssetsJSON(tt.json)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseAssetsJSON() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.wantErr {
				if tt.errMsg != "" && err.Error() != tt.errMsg {
					t.Errorf("ParseAssetsJSON() error = %q, want %q", err.Error(), tt.errMsg)
				}
				return
			}
			if total != tt.wantTotal {
				t.Errorf("ParseAssetsJSON() total = %d, want %d", total, tt.wantTotal)
			}
			if len(got) != len(tt.want) {
				t.Errorf("ParseAssetsJSON() got %d assets, want %d", len(got), len(tt.want))
				return
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("ParseAssetsJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestParseTreeJSON(t *testing.T) {
	tests := []struct {
		name    string
		json    string
		want    []CollectionNode
		wantErr bool
		errMsg  string
	}{
		{
			name: "empty tree",
			json: `{"collections":[]}`,
			want: []CollectionNode{},
		},
		{
			name: "nested tree",
			json: `{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"f2","name":"Italy 2024","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album","children":[]}]}]},{"id":"a2","name":"Favorites","kind":"album","children":[]}]}`,
			want: []CollectionNode{
				{ID: "f1", Name: "Trips", Kind: "folder", Children: []CollectionNode{{ID: "f2", Name: "Italy 2024", Kind: "folder", Children: []CollectionNode{{ID: "a1", Name: "Venice", Kind: "album"}}}}},
				{ID: "a2", Name: "Favorites", Kind: "album"},
			},
		},
		{
			name:    "error response",
			json:    `{"error":"library unavailable"}`,
			wantErr: true,
			errMsg:  "library unavailable",
		},
		{
			name:    "invalid json",
			json:    `not json`,
			wantErr: true,
		},
		{
			name: "missing collections key",
			json: `{}`,
			want: nil,
		},
		{
			name: "empty error is not an error",
			json: `{"error":"","collections":[{"id":"a9","name":"Root Album","kind":"album"}]}`,
			want: []CollectionNode{{ID: "a9", Name: "Root Album", Kind: "album"}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseTreeJSON(tt.json)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseTreeJSON() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.wantErr {
				if tt.errMsg != "" && err.Error() != tt.errMsg {
					t.Errorf("ParseTreeJSON() error = %q, want %q", err.Error(), tt.errMsg)
				}
				return
			}
			if len(got) != len(tt.want) {
				t.Errorf("ParseTreeJSON() got %d collections, want %d", len(got), len(tt.want))
				return
			}
			for i := range got {
				if !equalCollectionNode(got[i], tt.want[i]) {
					t.Errorf("ParseTreeJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestInterpretExportResult(t *testing.T) {
	tests := []struct {
		name     string
		rc       int
		wantN    int
		wantErr  bool
		errMsg   string
	}{
		{name: "success", rc: 5, wantN: 5},
		{name: "zero exports", rc: 0, wantN: 0},
		{name: "single export", rc: 1, wantN: 1},
		{name: "invalid args", rc: -1, wantErr: true, errMsg: "invalid arguments or directory creation failed"},
		{name: "mkdir failed", rc: -2, wantErr: true, errMsg: "could not create output directory"},
		{name: "album not found", rc: -3, wantErr: true, errMsg: "album not found"},
		{name: "all exports failed", rc: -4, wantErr: true, errMsg: "all exports failed"},
		{name: "cancelled", rc: -5, wantErr: true, errMsg: "cancelled"},
		{name: "unknown error", rc: -99, wantErr: true, errMsg: "unknown error (code -99)"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			n, err := InterpretExportResult(tt.rc)
			if (err != nil) != tt.wantErr {
				t.Errorf("InterpretExportResult() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.wantErr {
				if err.Error() != tt.errMsg {
					t.Errorf("InterpretExportResult() error = %q, want %q", err.Error(), tt.errMsg)
				}
				return
			}
			if n != tt.wantN {
				t.Errorf("InterpretExportResult() = %d, want %d", n, tt.wantN)
			}
		})
	}
}

func TestAlbumsResponseUnmarshal(t *testing.T) {
	raw := `{"albums":[{"id":"x","title":"Y"}]}`
	var resp AlbumsResponse
	if err := json.Unmarshal([]byte(raw), &resp); err != nil {
		t.Fatal(err)
	}
	if len(resp.Albums) != 1 || resp.Albums[0].ID != "x" || resp.Albums[0].Title != "Y" {
		t.Errorf("got %+v", resp.Albums)
	}
}

func TestAssetsResponseUnmarshal(t *testing.T) {
	raw := `{"assets":[{"id":"z","filename":"IMG_9999.JPG"}],"total":1}`
	var resp AssetsResponse
	if err := json.Unmarshal([]byte(raw), &resp); err != nil {
		t.Fatal(err)
	}
	if len(resp.Assets) != 1 || resp.Assets[0].ID != "z" || resp.Assets[0].Filename != "IMG_9999.JPG" {
		t.Errorf("got %+v", resp.Assets)
	}
	if resp.Total != 1 {
		t.Errorf("got total %d, want 1", resp.Total)
	}
}

func TestTreeResponseUnmarshal(t *testing.T) {
	raw := `{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]}`
	var resp TreeResponse
	if err := json.Unmarshal([]byte(raw), &resp); err != nil {
		t.Fatal(err)
	}
	if len(resp.Collections) != 1 || resp.Collections[0].ID != "f1" || resp.Collections[0].Name != "Trips" || len(resp.Collections[0].Children) != 1 || resp.Collections[0].Children[0].ID != "a1" || resp.Collections[0].Children[0].Name != "Venice" {
		t.Errorf("got %+v", resp.Collections)
	}
}

func TestErrorResponseUnmarshal(t *testing.T) {
	raw := `{"error":"oops"}`
	var resp ErrorResponse
	if err := json.Unmarshal([]byte(raw), &resp); err != nil {
		t.Fatal(err)
	}
	if resp.Error != "oops" {
		t.Errorf("got %q", resp.Error)
	}
}

func TestCgoBridgeImplementsBridge(t *testing.T) {
	var _ Bridge = &CgoBridge{}
}

func TestPackageLevelDelegatesToDefaultBridge(t *testing.T) {
	SetTestAccessRC(0)
	SetTestAlbumsJSON(`{"albums":[{"id":"p1","title":"PAlbum"}]}`)
	SetTestAssetsJSON(`{"assets":[{"id":"pa1","filename":"IMG_1001.JPG"}],"total":1}`)
	SetTestTreeJSON(`{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]}`)
	SetTestExportRC(3)
	SetTestExportOriginalsRC(2)
	SetTestBackupAllRC(5)

	if err := RequestAccess(); err != nil {
		t.Fatal(err)
	}

	albums, err := ListAlbums()
	if err != nil {
		t.Fatal(err)
	}
	if len(albums) != 1 || albums[0].ID != "p1" {
		t.Errorf("got %+v", albums)
	}

	assets, _, err := ListAssets("p1")
	if err != nil {
		t.Fatal(err)
	}
	if len(assets) != 1 || assets[0].ID != "pa1" || assets[0].Filename != "IMG_1001.JPG" {
		t.Errorf("got %+v", assets)
	}

	tree, err := ListTree()
	if err != nil {
		t.Fatal(err)
	}
	if len(tree) != 1 || tree[0].ID != "f1" || tree[0].Name != "Trips" || len(tree[0].Children) != 1 || tree[0].Children[0].ID != "a1" || tree[0].Children[0].Name != "Venice" {
		t.Errorf("got %+v", tree)
	}

	n, err := ExportAlbumPreviews("p1", "/tmp/x", 1024)
	if err != nil {
		t.Fatal(err)
	}
	if n != 3 {
		t.Errorf("got %d", n)
	}

	n, err = ExportAlbumOriginals("p1", "/tmp/x")
	if err != nil {
		t.Fatal(err)
	}
	if n != 2 {
		t.Errorf("got %d", n)
	}

	n, err = BackupAll("/tmp/x", 1024, false)
	if err != nil {
		t.Fatal(err)
	}
	if n != 5 {
		t.Errorf("got %d", n)
	}
}

func TestCgoBridgeListAlbumsViaStub(t *testing.T) {
	SetTestAlbumsJSON(`{"albums":[{"id":"abc","title":"TestAlbum"}]}`)
	bridge := &CgoBridge{}
	albums, err := bridge.ListAlbums()
	if err != nil {
		t.Fatal(err)
	}
	if len(albums) != 1 || albums[0].ID != "abc" || albums[0].Title != "TestAlbum" {
		t.Errorf("got %+v", albums)
	}
}

func TestCgoBridgeListAssetsViaStub(t *testing.T) {
	SetTestAssetsJSON(`{"assets":[{"id":"asset-1","filename":"A.JPG"},{"id":"asset-2","filename":"B.JPG"}],"total":2}`)
	bridge := &CgoBridge{}
	assets, _, err := bridge.ListAssets("any")
	if err != nil {
		t.Fatal(err)
	}
	if len(assets) != 2 {
		t.Errorf("got %d assets", len(assets))
	}
	if assets[0].Filename != "A.JPG" || assets[1].Filename != "B.JPG" {
		t.Errorf("got %+v", assets)
	}
}

func TestCgoBridgeListAssetsErrorViaStub(t *testing.T) {
	SetTestAssetsJSON(`{"error":"album not found"}`)
	bridge := &CgoBridge{}
	_, _, err := bridge.ListAssets("bad-id")
	if err == nil || err.Error() != "album not found" {
		t.Errorf("got %v", err)
	}
}

func TestCgoBridgeListTreeViaStub(t *testing.T) {
	SetTestTreeJSON(`{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"f2","name":"Italy 2024","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]},{"id":"a2","name":"Favorites","kind":"album"}]}`)
	bridge := &CgoBridge{}
	tree, err := bridge.ListTree()
	if err != nil {
		t.Fatal(err)
	}
	if len(tree) != 2 {
		t.Errorf("got %d collections", len(tree))
	}
	if tree[0].ID != "f1" || tree[0].Name != "Trips" || len(tree[0].Children) != 1 || tree[0].Children[0].ID != "f2" || tree[0].Children[0].Name != "Italy 2024" {
		t.Errorf("got %+v", tree)
	}
	if tree[1].ID != "a2" || tree[1].Name != "Favorites" || tree[1].Kind != "album" {
		t.Errorf("got %+v", tree)
	}
}

func TestCgoBridgeListTreeNil(t *testing.T) {
	SetTestTreeNull()
	defer SetTestTreeJSON(`{"collections":[]}`)
	bridge := &CgoBridge{}
	_, err := bridge.ListTree()
	if err != errBridgeNil {
		t.Errorf("got %v, want %v", err, errBridgeNil)
	}
}

func TestCgoBridgeExportViaStub(t *testing.T) {
	SetTestExportRC(7)
	bridge := &CgoBridge{}
	n, err := bridge.ExportAlbumPreviews("id", "/tmp/out", 512)
	if err != nil {
		t.Fatal(err)
	}
	if n != 7 {
		t.Errorf("got %d", n)
	}
}

func TestCgoBridgeExportErrorViaStub(t *testing.T) {
	SetTestExportRC(-3)
	bridge := &CgoBridge{}
	_, err := bridge.ExportAlbumPreviews("id", "/tmp/out", 512)
	if err == nil || err.Error() != "album not found" {
		t.Errorf("got %v", err)
	}
}

func TestCgoBridgeExportOriginalsViaStub(t *testing.T) {
	SetTestExportOriginalsRC(6)
	bridge := &CgoBridge{}
	n, err := bridge.ExportAlbumOriginals("id", "/tmp/out")
	if err != nil {
		t.Fatal(err)
	}
	if n != 6 {
		t.Errorf("got %d", n)
	}
}

func TestCgoBridgeExportOriginalsErrorViaStub(t *testing.T) {
	SetTestExportOriginalsRC(-4)
	bridge := &CgoBridge{}
	_, err := bridge.ExportAlbumOriginals("id", "/tmp/out")
	if err == nil || err.Error() != "all exports failed" {
		t.Errorf("got %v", err)
	}
}

func TestCgoBridgeBackupAllViaStub(t *testing.T) {
	SetTestBackupAllRC(8)
	bridge := &CgoBridge{}
	n, err := bridge.BackupAll("/tmp/out", 1024, false)
	if err != nil {
		t.Fatal(err)
	}
	if n != 8 {
		t.Errorf("got %d", n)
	}
}

func TestCgoBridgeBackupAllErrorViaStub(t *testing.T) {
	SetTestBackupAllRC(-2)
	bridge := &CgoBridge{}
	_, err := bridge.BackupAll("/tmp/out", 1024, true)
	if err == nil || err.Error() != "could not create output directory" {
		t.Errorf("got %v", err)
	}
}

func TestCgoBridgeRequestAccessGranted(t *testing.T) {
	SetTestAccessRC(0)
	bridge := &CgoBridge{}
	if err := bridge.RequestAccess(); err != nil {
		t.Errorf("got %v", err)
	}
}

func TestCgoBridgeRequestAccessDenied(t *testing.T) {
	SetTestAccessRC(-1)
	bridge := &CgoBridge{}
	err := bridge.RequestAccess()
	if err == nil {
		t.Fatal("expected error")
	}
	if err != errAccessDenied {
		t.Errorf("got %v, want %v", err, errAccessDenied)
	}
}

func TestCgoBridgeListAlbumsNil(t *testing.T) {
	SetTestAlbumsNull()
	defer SetTestAlbumsJSON(`{"albums":[]}`)
	bridge := &CgoBridge{}
	_, err := bridge.ListAlbums()
	if err != errBridgeNil {
		t.Errorf("got %v, want %v", err, errBridgeNil)
	}
}

func TestCgoBridgeListAssetsNil(t *testing.T) {
	SetTestAssetsNull()
	defer SetTestAssetsJSON(`{"assets":[],"total":0}`)
	bridge := &CgoBridge{}
	_, _, err := bridge.ListAssets("any")
	if err != errBridgeNil {
		t.Errorf("got %v, want %v", err, errBridgeNil)
	}
}

func TestErrAccessDeniedMessage(t *testing.T) {
	if errAccessDenied.Error() == "" {
		t.Error("errAccessDenied should have a message")
	}
}

func TestErrBridgeNilMessage(t *testing.T) {
	if errBridgeNil.Error() == "" {
		t.Error("errBridgeNil should have a message")
	}
}

func TestParseExportResultJSON(t *testing.T) {
	tests := []struct {
		name       string
		json       string
		want       ExportResult
		wantErr    bool
		errMsg     string
	}{
		{
			name: "success",
			json: `{"filename":"0000_test.jpg","size":1024,"cloud":"local"}`,
			want: ExportResult{Filename: "0000_test.jpg", Size: 1024, Cloud: "local"},
		},
		{
			name: "cloud asset",
			json: `{"filename":"photo.jpg","size":2048,"cloud":"cloud"}`,
			want: ExportResult{Filename: "photo.jpg", Size: 2048, Cloud: "cloud"},
		},
		{
			name:    "error response",
			json:    `{"error":"write failed","cloud":"local"}`,
			wantErr: true,
			errMsg:  "write failed",
		},
		{
			name:    "invalid json",
			json:    `not json`,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseExportResultJSON(tt.json)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseExportResultJSON() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.wantErr {
				if tt.errMsg != "" && err.Error() != tt.errMsg {
					t.Errorf("ParseExportResultJSON() error = %q, want %q", err.Error(), tt.errMsg)
				}
				return
			}
			if got != tt.want {
				t.Errorf("ParseExportResultJSON() = %+v, want %+v", got, tt.want)
			}
		})
	}
}

func equalCollectionNode(a, b CollectionNode) bool {
	if a.ID != b.ID || a.Name != b.Name || a.Kind != b.Kind || len(a.Children) != len(b.Children) {
		return false
	}
	for i := range a.Children {
		if !equalCollectionNode(a.Children[i], b.Children[i]) {
			return false
		}
	}
	return true
}
