package torrentfile

import (
	"crypto/sha1"
	"os"
	"path/filepath"
	"testing"
)

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

	info := "d6:lengthi5e4:name8:test.txt12:piece lengthi16384e6:pieces20:aaaaaaaaaaaaaaaaaaaa7:privatei1ee"
	torrent := "d8:announce14:http://tracker4:info" + info + "e"
	path := writeTempTorrent(t, torrent)

	tf, err := Open(path)
	if err != nil {
		t.Fatalf("Open returned error: %v", err)
	}

	expectedHash := sha1.Sum([]byte(info))
	if tf.InfoHash != expectedHash {
		t.Fatalf("unexpected info hash: got %x want %x", tf.InfoHash, expectedHash)
	}

	if tf.Announce != "http://tracker" {
		t.Fatalf("unexpected announce: got %q", tf.Announce)
	}
	if len(tf.Trackers) != 1 || tf.Trackers[0] != "http://tracker" {
		t.Fatalf("unexpected trackers list: %#v", tf.Trackers)
	}
	if tf.Name != "test.txt" {
		t.Fatalf("unexpected name: got %q", tf.Name)
	}
	if tf.PieceLength != 16384 {
		t.Fatalf("unexpected piece length: got %d", tf.PieceLength)
	}
	if tf.Length != 5 {
		t.Fatalf("unexpected length: got %d", tf.Length)
	}
	if len(tf.PieceHashes) != 1 {
		t.Fatalf("unexpected piece count: got %d", len(tf.PieceHashes))
	}
	if len(tf.Files) != 1 {
		t.Fatalf("unexpected file count: got %d", len(tf.Files))
	}
	if got, want := tf.Files[0].Path, "test.txt"; got != want {
		t.Fatalf("unexpected first file path: got %q want %q", got, want)
	}
	if got, want := tf.Files[0].Length, 5; got != want {
		t.Fatalf("unexpected first file length: got %d want %d", got, want)
	}

	var expectedPiece [20]byte
	copy(expectedPiece[:], []byte("aaaaaaaaaaaaaaaaaaaa"))
	if tf.PieceHashes[0] != expectedPiece {
		t.Fatalf("unexpected piece hash: got %x want %x", tf.PieceHashes[0], expectedPiece)
	}
}

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

	info := "d6:lengthi5e4:name8:test.txt12:piece lengthi16384e6:pieces19:aaaaaaaaaaaaaaaaaaae"
	torrent := "d8:announce14:http://tracker4:info" + info + "e"
	path := writeTempTorrent(t, torrent)

	_, err := Open(path)
	if err == nil {
		t.Fatal("expected Open to fail for invalid piece hash length")
	}
}

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

	pieces := "aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb"
	info := "d5:filesld6:lengthi3e4:pathl5:a.txteed6:lengthi2e4:pathl5:b.txteee4:name4:root12:piece lengthi4e6:pieces40:" + pieces + "e"
	torrent := "d8:announce14:http://tracker4:info" + info + "e"
	path := writeTempTorrent(t, torrent)

	tf, err := Open(path)
	if err != nil {
		t.Fatalf("Open returned error: %v", err)
	}

	if tf.Length != 5 {
		t.Fatalf("unexpected total length: got %d", tf.Length)
	}
	if len(tf.Files) != 2 {
		t.Fatalf("unexpected file count: got %d", len(tf.Files))
	}
	if got, want := tf.Files[0].Path, "root/a.txt"; got != want {
		t.Fatalf("unexpected file[0] path: got %q want %q", got, want)
	}
	if got, want := tf.Files[1].Path, "root/b.txt"; got != want {
		t.Fatalf("unexpected file[1] path: got %q want %q", got, want)
	}
	if len(tf.PieceHashes) != 2 {
		t.Fatalf("unexpected piece hash count: got %d", len(tf.PieceHashes))
	}
}

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

	info := "d6:lengthi10e4:name8:test.txt12:piece lengthi4e6:pieces40:aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbe"
	torrent := "d8:announce14:http://tracker4:info" + info + "e"
	path := writeTempTorrent(t, torrent)

	_, err := Open(path)
	if err == nil {
		t.Fatal("expected Open to fail for piece count mismatch")
	}
}

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

	info := "d6:lengthi5e4:name8:test.txt12:piece lengthi16384e6:pieces20:aaaaaaaaaaaaaaaaaaaae"
	torrent := "d8:announce14:http://tracker13:announce-listll14:http://tracker17:udp://tracker.oneel17:udp://tracker.one17:udp://tracker.twoee4:info" + info + "e"
	path := writeTempTorrent(t, torrent)

	tf, err := Open(path)
	if err != nil {
		t.Fatalf("Open returned error: %v", err)
	}

	expected := []string{
		"http://tracker",
		"udp://tracker.one",
		"udp://tracker.two",
	}
	if len(tf.Trackers) != len(expected) {
		t.Fatalf("unexpected trackers length: got %d want %d (%#v)", len(tf.Trackers), len(expected), tf.Trackers)
	}
	for i := range expected {
		if tf.Trackers[i] != expected[i] {
			t.Fatalf("unexpected tracker[%d]: got %q want %q", i, tf.Trackers[i], expected[i])
		}
	}
}

func writeTempTorrent(t *testing.T, data string) string {
	t.Helper()

	dir := t.TempDir()
	path := filepath.Join(dir, "test.torrent")
	if err := os.WriteFile(path, []byte(data), 0o600); err != nil {
		t.Fatalf("failed to write temp torrent file: %v", err)
	}

	return path
}
