package githubapp

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

func TestParseConnectionID(t *testing.T) {
	id, err := ParseConnectionID("github-app-installation:123")
	if err != nil || id != 123 {
		t.Fatalf("id=%d err=%v", id, err)
	}
	for _, value := range []string{"123", "github-app-installation:0", "github-app-installation:nope"} {
		if _, err := ParseConnectionID(value); err == nil {
			t.Fatalf("ParseConnectionID(%q) unexpectedly succeeded", value)
		}
	}
}

func TestVerifyInstallationChangeWaitsForNewInstallation(t *testing.T) {
	installationReads := 0
	resolver := &Resolver{config: Config{
		APIURL: "https://api.github.test", WebURL: "https://github.test",
		ClientID: "client-id", ClientSecret: "client-secret",
		InstallationDiscoveryAttempts: 3, InstallationDiscoveryInterval: time.Millisecond,
		HTTPClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			body := ""
			switch req.URL.String() {
			case "https://github.test/login/oauth/access_token":
				body = `{"access_token":"user-token"}`
			case "https://api.github.test/user":
				body = `{"id":9001,"login":"octocat"}`
			case "https://api.github.test/user/installations?per_page=100&page=1":
				installationReads++
				if installationReads == 1 {
					body = `{"installations":[{"id":41,"account":{"login":"octocat","type":"User"}}]}`
				} else {
					body = `{"installations":[{"id":41,"account":{"login":"octocat","type":"User"}},{"id":42,"account":{"login":"clawmem-ai","type":"Organization"}}]}`
				}
			default:
				t.Fatalf("unexpected request %s", req.URL)
			}
			return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}, nil
		})},
	}}

	authorization, err := resolver.VerifyInstallationChange(context.Background(), "callback-code", 0, []int64{41})
	if err != nil {
		t.Fatalf("VerifyInstallationChange: %v", err)
	}
	if installationReads != 2 || len(authorization.Installations) != 2 || authorization.Installations[1].ID != 42 {
		t.Fatalf("reads=%d authorization=%#v", installationReads, authorization)
	}
}

func TestVerifyInstallationChangeRejectsStaleInstallationList(t *testing.T) {
	resolver := &Resolver{config: Config{
		APIURL: "https://api.github.test", WebURL: "https://github.test",
		ClientID: "client-id", ClientSecret: "client-secret",
		InstallationDiscoveryAttempts: 2, InstallationDiscoveryInterval: time.Millisecond,
		HTTPClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			body := ""
			switch req.URL.String() {
			case "https://github.test/login/oauth/access_token":
				body = `{"access_token":"user-token"}`
			case "https://api.github.test/user":
				body = `{"id":9001,"login":"octocat"}`
			case "https://api.github.test/user/installations?per_page=100&page=1":
				body = `{"installations":[{"id":41,"account":{"login":"octocat","type":"User"}}]}`
			default:
				t.Fatalf("unexpected request %s", req.URL)
			}
			return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}, nil
		})},
	}}

	if _, err := resolver.VerifyInstallationChange(context.Background(), "callback-code", 0, []int64{41}); err == nil || !strings.Contains(err.Error(), "did not become visible") {
		t.Fatalf("VerifyInstallationChange error = %v, want stale-list rejection", err)
	}
}

type roundTripFunc func(*http.Request) (*http.Response, error)

func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }

func TestInstallURLAndVerifyInstallation(t *testing.T) {
	resolver := &Resolver{config: Config{
		APIURL: "https://api.github.test", WebURL: "https://github.test", AppSlug: "ags-sync",
		ClientID: "client-id", ClientSecret: "client-secret",
		HTTPClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			body := ""
			switch req.URL.String() {
			case "https://github.test/login/oauth/access_token":
				if req.Method != http.MethodPost {
					t.Fatalf("OAuth method = %s", req.Method)
				}
				payload, _ := io.ReadAll(req.Body)
				if !strings.Contains(string(payload), "code=callback-code") {
					t.Fatalf("OAuth form = %s", payload)
				}
				body = `{"access_token":"user-token"}`
			case "https://api.github.test/user":
				body = `{"id":9001,"login":"octocat","avatar_url":"user-avatar"}`
			case "https://api.github.test/user/installations?per_page=100&page=1":
				if req.Header.Get("Authorization") != "Bearer user-token" {
					t.Fatalf("Authorization = %q", req.Header.Get("Authorization"))
				}
				body = `{"installations":[{"id":42,"repository_selection":"selected","permissions":{"contents":"write"},"html_url":"https://github.test/settings/installations/42","account":{"login":"pingcap","type":"Organization","avatar_url":"avatar"}}]}`
			default:
				t.Fatalf("unexpected request %s", req.URL)
			}
			return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}, nil
		})},
	}}
	installURL, err := resolver.InstallURL("opaque-state")
	if err != nil || installURL != "https://github.test/apps/ags-sync/installations/new?state=opaque-state" {
		t.Fatalf("InstallURL = %q, %v", installURL, err)
	}
	authorizeURL, err := resolver.AuthorizeURL("opaque-state")
	if err != nil || authorizeURL != "https://github.test/login/oauth/authorize?client_id=client-id&state=opaque-state" {
		t.Fatalf("AuthorizeURL = %q, %v", authorizeURL, err)
	}
	authorization, err := resolver.VerifyInstallation(context.Background(), "callback-code", 42)
	if err != nil {
		t.Fatalf("VerifyInstallation: %v", err)
	}
	if authorization.User.Login != "octocat" || len(authorization.Installations) != 1 || authorization.Installations[0].ID != 42 || authorization.Installations[0].AccountLogin != "pingcap" {
		t.Fatalf("authorization = %#v", authorization)
	}
	authorizations, err := resolver.VerifyAuthorizations(context.Background(), "callback-code")
	if err != nil || authorizations.User.ID != 9001 || len(authorizations.Installations) != 1 || authorizations.Installations[0].ID != 42 {
		t.Fatalf("VerifyAuthorizations = %#v, %v", authorizations, err)
	}
}
