package debian

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"net/http"
	"strings"
	"testing"
)

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

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

func TestResolveSourceHTTPFallbackToGzip(t *testing.T) {
	t.Parallel()

	sources := strings.TrimSpace(`
Package: neofetch
Version: 7.1.0-4
Directory: pool/main/n/neofetch
Checksums-Sha256:
 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1978 neofetch_7.1.0-4.dsc
 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 73791 neofetch_7.1.0.orig.tar.gz
`) + "\n"

	var gz bytes.Buffer
	zw := gzip.NewWriter(&gz)
	if _, err := zw.Write([]byte(sources)); err != nil {
		t.Fatalf("failed to write gzip sources: %v", err)
	}
	if err := zw.Close(); err != nil {
		t.Fatalf("failed to close gzip writer: %v", err)
	}

	client := &http.Client{
		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			switch req.URL.Path {
			case "/debian/dists/stable/main/source/Sources.xz":
				return httpResponse(req, http.StatusNotFound, []byte("not found")), nil
			case "/debian/dists/stable/main/source/Sources.gz":
				return httpResponse(req, http.StatusOK, gz.Bytes()), nil
			default:
				return httpResponse(req, http.StatusNotFound, []byte("missing")), nil
			}
		}),
	}

	r := NewResolver(
		WithHTTPClient(client),
		WithMirrors([]string{"https://mirror.example/debian"}),
		WithSuites([]string{"stable"}),
		WithComponents([]string{"main"}),
	)

	info, err := r.ResolveSource("neofetch")
	if err != nil {
		t.Fatalf("ResolveSource() error = %v", err)
	}

	if info.SourcePackage != "neofetch" {
		t.Fatalf("unexpected source package: %s", info.SourcePackage)
	}
	if info.DebianVersion != "7.1.0-4" {
		t.Fatalf("unexpected debian version: %s", info.DebianVersion)
	}
	if info.UpstreamVersion != "7.1.0" {
		t.Fatalf("unexpected upstream version: %s", info.UpstreamVersion)
	}
	wantURL := "https://mirror.example/debian/pool/main/n/neofetch/neofetch_7.1.0-4.dsc"
	if info.DSCURL != wantURL {
		t.Fatalf("unexpected dsc url:\nwant: %s\ngot:  %s", wantURL, info.DSCURL)
	}
}

func TestResolveSourceHTTPNotFound(t *testing.T) {
	t.Parallel()

	client := &http.Client{
		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return httpResponse(req, http.StatusNotFound, []byte("not found")), nil
		}),
	}

	r := NewResolver(
		WithHTTPClient(client),
		WithMirrors([]string{"https://mirror.example/debian"}),
		WithSuites([]string{"stable"}),
		WithComponents([]string{"main"}),
	)

	_, err := r.ResolveSource("no-such-package")
	if err == nil {
		t.Fatalf("expected not found error")
	}
	if !strings.Contains(err.Error(), "not found") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestParseSourcesParagraph(t *testing.T) {
	t.Parallel()

	paragraph := []string{
		"Package: foo",
		"Version: 1:2.3.4-5",
		"Directory: pool/main/f/foo",
		"Checksums-Sha256:",
		" aaaa 10 foo_1:2.3.4-5.dsc",
		" bbbb 20 foo_2.3.4.orig.tar.gz",
	}

	rec, err := parseSourcesParagraph(paragraph)
	if err != nil {
		t.Fatalf("parseSourcesParagraph() error = %v", err)
	}
	if rec.Package != "foo" || rec.Version != "1:2.3.4-5" {
		t.Fatalf("unexpected record identity: %s %s", rec.Package, rec.Version)
	}
	if rec.Directory != "pool/main/f/foo" {
		t.Fatalf("unexpected directory: %s", rec.Directory)
	}
	if rec.DSCName != "foo_1:2.3.4-5.dsc" {
		t.Fatalf("unexpected dsc name: %s", rec.DSCName)
	}
	if rec.DSCSHA256 != "aaaa" {
		t.Fatalf("unexpected dsc hash: %s", rec.DSCSHA256)
	}
	if got := normalizeUpstreamVersion(rec.Version); got != "2.3.4" {
		t.Fatalf("unexpected normalized version: %s", got)
	}
}

func TestResolveSourceInvalidPackageName(t *testing.T) {
	t.Parallel()

	r := NewResolver()
	_, err := r.ResolveSource("bad/name")
	if err == nil {
		t.Fatalf("expected package name validation error")
	}
}

func httpResponse(req *http.Request, status int, body []byte) *http.Response {
	return &http.Response{
		StatusCode: status,
		Body:       io.NopCloser(bytes.NewReader(body)),
		Header:     make(http.Header),
		Request:    req,
		Status:     fmt.Sprintf("%d %s", status, http.StatusText(status)),
	}
}
