package github

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"testing"

	"github.com/Snider/Borg/pkg/mocks"
	"github.com/google/go-github/v39/github"
)

type errorRoundTripper struct{}

func (e *errorRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return nil, fmt.Errorf("do error")
}

func TestParseRepoFromURL(t *testing.T) {
	testCases := []struct {
		url       string
		owner     string
		repo      string
		expectErr bool
	}{
		{"https://github.com/owner/repo.git", "owner", "repo", false},
		{"http://github.com/owner/repo", "owner", "repo", false},
		{"git://github.com/owner/repo.git", "owner", "repo", false},
		{"github.com/owner/repo", "owner", "repo", false},
		{"git@github.com:owner/repo.git", "owner", "repo", false},
		{"https://github.com/owner/repo/tree/main", "", "", true},
		{"invalid-url", "", "", true},
		{"git:github.com:owner/repo.git", "owner", "repo", false},
	}

	for _, tc := range testCases {
		owner, repo, err := ParseRepoFromURL(tc.url)
		if (err != nil) != tc.expectErr {
			t.Errorf("unexpected error for URL %s: %v", tc.url, err)
		}
		if owner != tc.owner || repo != tc.repo {
			t.Errorf("unexpected owner/repo for URL %s: %s/%s", tc.url, owner, repo)
		}
	}
}

func TestGetLatestRelease(t *testing.T) {
	oldNewClient := NewClient
	t.Cleanup(func() { NewClient = oldNewClient })

	mockClient := mocks.NewMockClient(map[string]*http.Response{
		"https://api.github.com/repos/owner/repo/releases/latest": {
			StatusCode: http.StatusOK,
			Body:       io.NopCloser(bytes.NewBufferString(`{"tag_name": "v1.0.0"}`)),
		},
	})
	client := github.NewClient(mockClient)
	NewClient = func(_ *http.Client) *github.Client {
		return client
	}

	release, err := GetLatestRelease("owner", "repo")
	if err != nil {
		t.Fatalf("GetLatestRelease failed: %v", err)
	}

	if release.GetTagName() != "v1.0.0" {
		t.Errorf("unexpected tag name: %s", release.GetTagName())
	}
}

func TestDownloadReleaseAsset(t *testing.T) {
	mockClient := mocks.NewMockClient(map[string]*http.Response{
		"https://github.com/owner/repo/releases/download/v1.0.0/asset.zip": {
			StatusCode: http.StatusOK,
			Body:       io.NopCloser(bytes.NewBufferString("asset content")),
		},
	})

	asset := &github.ReleaseAsset{
		BrowserDownloadURL: github.String("https://github.com/owner/repo/releases/download/v1.0.0/asset.zip"),
	}

	oldClient := DefaultClient
	DefaultClient = mockClient
	defer func() {
		DefaultClient = oldClient
	}()

	data, err := DownloadReleaseAsset(asset)
	if err != nil {
		t.Fatalf("DownloadReleaseAsset failed: %v", err)
	}

	if string(data) != "asset content" {
		t.Errorf("unexpected asset content: %s", string(data))
	}
}
func TestDownloadReleaseAsset_BadRequest(t *testing.T) {
	mockClient := mocks.NewMockClient(map[string]*http.Response{
		"https://github.com/owner/repo/releases/download/v1.0.0/asset.zip": {
			StatusCode: http.StatusBadRequest,
			Status:     "400 Bad Request",
			Body:       io.NopCloser(bytes.NewBufferString("")),
		},
	})
	expectedErr := "bad status: 400 Bad Request"

	asset := &github.ReleaseAsset{
		BrowserDownloadURL: github.String("https://github.com/owner/repo/releases/download/v1.0.0/asset.zip"),
	}

	oldClient := DefaultClient
	DefaultClient = mockClient
	defer func() {
		DefaultClient = oldClient
	}()

	_, err := DownloadReleaseAsset(asset)
	if err == nil {
		t.Fatalf("expected error but got nil")
	}
	if err.Error() != expectedErr {
		t.Fatalf("DownloadReleaseAsset failed: %v", err)
	}
}

func TestDownloadReleaseAsset_NewRequestError(t *testing.T) {
	errRequest := fmt.Errorf("bad request")
	asset := &github.ReleaseAsset{
		BrowserDownloadURL: github.String("https://github.com/owner/repo/releases/download/v1.0.0/asset.zip"),
	}

	oldNewRequest := NewRequest
	NewRequest = func(method, url string, body io.Reader) (*http.Request, error) {
		return nil, errRequest
	}
	defer func() {
		NewRequest = oldNewRequest
	}()

	_, err := DownloadReleaseAsset(asset)
	if err == nil {
		t.Fatalf("expected error but got nil")
	}
}

func TestGetLatestRelease_Error(t *testing.T) {
	oldNewClient := NewClient
	t.Cleanup(func() { NewClient = oldNewClient })

	u, _ := url.Parse("https://api.github.com/repos/owner/repo/releases/latest")
	mockClient := mocks.NewMockClient(map[string]*http.Response{
		"https://api.github.com/repos/owner/repo/releases/latest": {
			StatusCode: http.StatusNotFound,
			Status:     "404 Not Found",
			Header:     http.Header{"Content-Type": []string{"application/json"}},
			Body:       io.NopCloser(bytes.NewBufferString("")),
			Request:    &http.Request{Method: "GET", URL: u},
		},
	})
	expectedErr := "GET https://api.github.com/repos/owner/repo/releases/latest: 404  []"
	client := github.NewClient(mockClient)
	NewClient = func(_ *http.Client) *github.Client {
		return client
	}

	_, err := GetLatestRelease("owner", "repo")
	if err == nil {
		t.Fatalf("expected error but got nil")
	}
	if err.Error() != expectedErr {
		t.Fatalf("GetLatestRelease failed: %v", err)
	}
}

func TestDownloadReleaseAsset_DoError(t *testing.T) {
	mockClient := &http.Client{
		Transport: &errorRoundTripper{},
	}

	asset := &github.ReleaseAsset{
		BrowserDownloadURL: github.String("https://github.com/owner/repo/releases/download/v1.0.0/asset.zip"),
	}

	oldClient := DefaultClient
	DefaultClient = mockClient
	defer func() {
		DefaultClient = oldClient
	}()

	_, err := DownloadReleaseAsset(asset)
	if err == nil {
		t.Fatalf("DownloadReleaseAsset should have failed")
	}
}
