package ledger

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

func TestFileAmend(t *testing.T) {
	cases := map[string]struct {
		from string
		old  Delta
		now  Delta
		want string
	}{
		"multi recipient": {
			from: `
2006-01-02  description
               recipientA  $3.45
               recipientB  $6.45
               payee
         `,
			old: Delta{
				Date:        "2006-01-02",
				Name:        "recipientB",
				Value:       6.45,
				Currency:    "$",
				Description: "description",
			},
			now: Delta{
				Date:        "2106-11-12",
				Name:        "recipientC",
				Value:       16.45,
				Currency:    "T",
				Description: "1description",
			},
			want: `
2006-01-02   description
	recipientB			$-6.45
	payee
2106-11-12   1description
	recipientC			16.45 T
	payee`,
		},
		"recipient": {
			from: `
2006-01-02  description
               recipient  $3.45
               payee      $-3.45
         `,
			old: Delta{
				Date:        "2006-01-02",
				Name:        "recipient",
				Value:       3.45,
				Currency:    "$",
				Description: "description",
			},
			now: Delta{
				Date:        "2106-11-12",
				Name:        "1recipient",
				Value:       13.45,
				Currency:    "T",
				Description: "1description",
			},
			want: `
2006-01-02   description
	recipient			$-3.45
	payee
2106-11-12   1description
	1recipient			13.45 T
	payee`,
		},
		"payee": {
			from: `
2006-01-02=2006-01-03  description
               recipient  $3.45
               payee
         `,
			old: Delta{
				Date:        "2006-01-02",
				OtherDates:  []string{"2006-01-03"},
				Name:        "payee",
				Value:       -3.45,
				Currency:    "$",
				Description: "description",
			},
			now: Delta{
				Date:        "2106-11-12",
				OtherDates:  []string{"2006-01-03"},
				Name:        "1payee",
				Value:       -13.45,
				Currency:    "T",
				Description: "1description",
			},
			want: `
2006-01-02=2006-01-03   description
	payee			$3.45
	recipient
2106-11-12=2006-01-03   1description
	1payee			-13.45 T
	recipient`,
		},
		"was set": {
			from: `
2006-01-02  description
               recipient  $3.45
               payee
         `,
			old: Delta{
				Date:        "2006-01-02",
				Name:        "recipient",
				Value:       3.45,
				Currency:    "$",
				Description: "description",
			},
			now: Delta{
				Date:        "2106-11-12",
				Name:        "1recipient",
				Value:       13.45,
				Currency:    "T",
				Description: "1description",
			},
			want: `
2006-01-02   description
	recipient			$-3.45
	payee
2106-11-12   1description
	1recipient			13.45 T
	payee`,
		},
	}

	for name, d := range cases {
		c := d
		t.Run(name, func(t *testing.T) {
			p := path.Join(t.TempDir(), "dat")
			if err := os.WriteFile(p, []byte(c.from), os.ModePerm); err != nil {
				t.Fatal(err)
			}

			files, err := NewFiles(p)
			if err != nil {
				t.Fatal(err)
			} else if deltas, err := files.Deltas(); err != nil {
				t.Fatal(err)
			} else if filtered := deltas.Like(func(d Delta) bool {
				c.old.Transaction = d.Transaction
				c.old.Payee = d.Payee
				return d.equivalent(c.old)
			}); len(filtered) != 1 {
				t.Fatalf("input \n\t%s \ndidnt include old \n\t%+v \nin \n\t%+v: \n\t%+v", c.from, c.old, deltas, filtered)
			}

			if err := files.Amend(c.old, c.now); err != nil {
				t.Fatal(err)
			} else if b, err := os.ReadFile(path.Join(path.Dir(p), "inbox.txt")); err != nil {
				t.Fatal(err)
			} else if string(b) != c.want {
				t.Fatalf("expected \n\t%q\nbut got\n\t%q\n\t%s", c.want, b, b)
			}
		})
	}
}

func TestFileAdd(t *testing.T) {
	filesAppendDelim = " "
	payee := "name:3"
	delta := Delta{
		Date:        "2999-88-77",
		Description: "66",
		Name:        "name:1",
		Value:       2.00,
		Currency:    USD,
	}
	cases := map[string]struct {
		given []byte
		want  string
	}{
		"no file": {
			given: nil,
			want: `
2999-88-77   66
 name:1   $2.00
 name:3`,
		},
		"empty file": {
			given: []byte{},
			want: `
2999-88-77   66
 name:1   $2.00
 name:3`,
		},
		"happy without trailing whitespace": {
			given: []byte(`
2000-01-02 desc
  name:1 $1.00
  name:2 $-1.00`),
			want: `
2000-01-02 desc
  name:1 $1.00
  name:2 $-1.00
2999-88-77   66
 name:1   $2.00
 name:3`,
		},
		"happy with trailing newline": {
			given: []byte(`
2000-01-02 desc
  name:1 $1.00
  name:2 $-1.00
         `),
			want: `
2000-01-02 desc
  name:1 $1.00
  name:2 $-1.00
2999-88-77   66
 name:1   $2.00
 name:3`,
		},
	}

	for name, d := range cases {
		c := d
		t.Run(name, func(t *testing.T) {
			p := path.Join(t.TempDir(), "inbox.txt")
			if c.given != nil {
				if err := os.WriteFile(p, []byte(c.given), os.ModePerm); err != nil {
					t.Fatal(err)
				}
			}

			f, err := NewFiles(p)
			if err != nil {
				t.Fatal(err)
			}

			if err := f.Add(payee, delta); err != nil {
				t.Fatal(err)
			}

			if got, err := os.ReadFile(p); err != nil {
				t.Fatal(err)
			} else if string(got) != c.want {
				t.Errorf("wanted\n\t%s, got\n\t%s", c.want, got)
			}
		})
	}
}

func TestFileTestdataMacroWithBPI(t *testing.T) {
	paths, err := filepath.Glob("./testdata/macro.d/*")
	if err != nil {
		t.Fatal(err)
	}
	t.Log(paths)

	f, err := NewFiles(paths[0], paths[1:]...)
	if err != nil {
		t.Fatal(err)
	}

	deltas, err := f.Deltas()
	if err != nil {
		t.Fatal(err)
	}

	bpis, err := NewBPIs("./testdata/bpi.bpi")
	if err != nil {
		t.Fatal(err)
	}

	t.Run("bal like", func(t *testing.T) {
		bal := deltas.Like(LikeName(`^AssetAccount:Bond`)).Balances().WithBPIs(bpis)
		for k, v := range bal {
			t.Logf("%s: %+v", k, v)
		}
	})

	t.Run("reg like", func(t *testing.T) {
		reg := deltas.Like(LikeName(`^AssetAccount:Bond`)).Register()
		for k, v := range reg {
			t.Logf("%s: %+v", k, v.WithBPIs(bpis))
		}
	})
}

func TestFileTestdata(t *testing.T) {
	t.Run("macro.d", func(t *testing.T) {
		paths, err := filepath.Glob("./testdata/macro.d/*")
		if err != nil {
			t.Fatal(err)
		}

		f, err := NewFiles(paths[0], paths[1:]...)
		if err != nil {
			t.Fatal(err)
		}

		deltas, err := f.Deltas()
		if err != nil {
			t.Fatal(err)
		}

		t.Run("deltas", func(t *testing.T) {
			for i := range deltas {
				t.Logf("%+v", deltas[i].Debug())
			}
		})

		t.Run("balances", func(t *testing.T) {
			balances := deltas.Balances()
			for k, v := range balances {
				t.Logf("%s: %+v", k, v)
			}
		})

		t.Run("balances like", func(t *testing.T) {
			balances := deltas.Like(LikeName(`^AssetAccount:`)).Balances()
			for k, v := range balances {
				t.Logf("%s: %+v", k, v)
			}
		})
	})

	t.Run("single files", func(t *testing.T) {
		paths, err := filepath.Glob("./testdata/*.dat")
		if err != nil {
			t.Fatal(err)
		}

		for _, pathd := range paths {
			path := pathd
			t.Run(path, func(t *testing.T) {
				f, err := NewFiles(path)
				if err != nil {
					t.Fatal(err)
				}

				deltas, err := f.Deltas()
				if err != nil {
					t.Fatal(err)
				}

				t.Run("deltas", func(t *testing.T) {
					for i := range deltas {
						t.Logf("%+v", deltas[i].Debug())
					}
				})

				t.Run("balances", func(t *testing.T) {
					balances := deltas.Balances()
					for k, v := range balances {
						t.Logf("%s: %+v", k, v)
					}
				})

				t.Run("balances like", func(t *testing.T) {
					balances := deltas.Like(LikeName(`AssetAccount:Cash:Fidelity76`)).Balances()
					for k, v := range balances {
						t.Logf("%s: %+v", k, v)
					}
				})
			})
		}
	})
}

func TestFileDeltas(t *testing.T) {
	happy := []Delta{
		{
			Date:        "2022-12-12",
			Name:        "AssetAccount:Cash:Fidelity76",
			Value:       -97.92,
			Currency:    USD,
			Description: "Electricity / Power Bill TG2PJ-2PLP5",
			Payee:       true,

			fileName: "",
			lineNo:   0,
		},
		{
			Date:        "2022-12-12",
			Name:        "Withdrawal:0:SharedHome:DominionEnergy",
			Value:       97.92,
			Currency:    USD,
			Description: "Electricity / Power Bill TG2PJ-2PLP5",

			fileName: "",
			lineNo:   0,
		},
		{
			Date:        "2022-12-12",
			Name:        "AssetAccount:Cash:Fidelity76",
			Value:       -1.00,
			Currency:    USD,
			Description: "Test pay chase TG32S-BT2FF",
			Payee:       true,

			fileName: "",
			lineNo:   0,
		},
		{
			Date:        "2022-12-12",
			Name:        "Debts:Credit:ChaseFreedomUltdVisa",
			Value:       1.00,
			Currency:    USD,
			Description: "Test pay chase TG32S-BT2FF",

			fileName: "",
			lineNo:   0,
		},
	}

	cases := map[string][]Delta{
		"empty": nil,
		"one":   happy[:2],
		"happy": happy[:],
	}

	for name, d := range cases {
		want := d
		t.Run(name, func(t *testing.T) {
			fileName := "./testdata/" + name + ".dat"
			f, err := NewFiles(fileName)
			if err != nil {
				t.Fatal(err)
			}

			deltas, err := f.Deltas()
			if err != nil {
				t.Fatal(err)
			}

			if len(deltas) != len(want) {
				t.Error(len(deltas))
			}
			for i := range want {
				want[i].fileName = fileName
				deltas[i].lineNo = 0
				if i >= len(deltas) {
					break
				}
				if deltas[i].Transaction == "" {
					t.Error(deltas[i].Transaction)
				}
				deltas[i].Transaction = ""
				if !want[i].equivalent(deltas[i]) {
					t.Errorf("[%d] \n\twant=%s, \n\t got=%s", i, want[i].Debug(), deltas[i].Debug())
				}
			}
		})
	}
}

func TestFilesOfDir(t *testing.T) {
	d := t.TempDir()
	files := Files([]string{d, "/dev/null"})
	if paths := files.paths(); len(paths) != 1 {
		t.Error(paths)
	}

	os.WriteFile(path.Join(d, "1"), []byte{}, os.ModePerm)
	os.Mkdir(path.Join(d, "d2"), os.ModePerm)
	os.WriteFile(path.Join(d, "d2", "2"), []byte{}, os.ModePerm)
	if paths := files.paths(); len(paths) != 3 || paths[0] != path.Join(d, "1") || paths[1] != path.Join(d, "d2", "2") {
		t.Error(paths)
	}
}
