package analyzer

import (
	"fmt"
	"io"
	"path/filepath"
	"sort"
	"strings"
	"testing"

	"olexsmir.xyz/clerk/internal/testutil/golden"
	"olexsmir.xyz/clerk/journal"
	"olexsmir.xyz/clerk/journal/ast"
)

func TestAnalysis(t *testing.T) {
	tests := []string{
		"empty",
		"include",
		"journal",
		"typed-entries",
		"account-indexing",
		"account-usage",
		"commodity-usage",
		"payees",
		"periodic-automated",
		"directives",
		"cost-balance",
		"posting-types",
		"dates",
		"tags",
		"decimal-marks",
		"duplicate-transactions",
	}

	for _, tname := range tests {
		t.Run(tname, func(t *testing.T) {
			a := golden.Read(t, tname)
			fsys, err := a.FS()
			if err != nil {
				t.Fatal(err)
			}

			l := journal.NewLoader()
			rj, err := l.ResolveFS(fsys, "in.journal")
			if err != nil {
				t.Fatalf("failed to load test journal: %v", err)
			}

			ctx := Build(rj)
			var b strings.Builder
			fprint(&b, ctx)
			golden.Assert(t, a, b.String())
		})
	}
}

func fprint(w io.Writer, a *Analysis) {
	fmt.Fprintf(w, "files (%d):\n", len(a.Files))
	for i, pf := range a.Files {
		fmt.Fprintf(w, "  %d: %s\n", i, filepath.Base(pf.Path))
	}

	// commodities
	csyms := make([]string, 0, len(a.Commodities))
	for sym := range a.Commodities {
		csyms = append(csyms, sym)
	}
	sort.Strings(csyms)

	fmt.Fprintf(w, "\ncommodities (%d):\n", len(a.Commodities))
	for _, sym := range csyms {
		info := a.Commodities[sym]
		fmt.Fprintf(w, "  %s\n", sym)
		fmt.Fprintf(w, "    directives: %d\n", len(info.Directives))
		fmt.Fprintf(w, "    used: %d\n", info.UsedCount)
		for _, cd := range info.Directives {
			fmt.Fprintf(w, "      commodity %s\n", cd.Commodity)
		}
		fmt.Fprintf(w, "    usages: %d\n", len(info.Usages))
		for _, u := range info.Usages {
			fmt.Fprintf(w, "      file %d: %s (%q)\n", u.FileIndex, u.Amount.Commodity, u.Amount.Quantity.String())
		}
		if info.LastUsed.Year != 0 {
			fmt.Fprintf(w, "    last-used: %d-%02d-%02d\n", info.LastUsed.Year, info.LastUsed.Month, info.LastUsed.Day)
		}
	}

	// accounts
	anames := make([]string, 0, len(a.Accounts))
	for name := range a.Accounts {
		anames = append(anames, name)
	}
	sort.Strings(anames)

	fmt.Fprintf(w, "\naccounts (%d):\n", len(a.Accounts))
	for _, name := range anames {
		info := a.Accounts[name]
		fmt.Fprintf(w, "  %s\n", name)
		fmt.Fprintf(w, "    directives: %d\n", len(info.Directives))
		fmt.Fprintf(w, "    used: %d\n", info.UsedCount)
		for _, d := range info.Directives {
			fmt.Fprintf(w, "      account %s\n", d.Account.String())
		}
		fmt.Fprintf(w, "    usages: %d\n", len(info.Usages))
		for _, u := range info.Usages {
			fmt.Fprintf(w, "      file %d: %s\n", u.FileIndex, u.Posting.Account.String())
		}
		if info.LastUsed.Year != 0 {
			fmt.Fprintf(w, "    last-used: %d-%02d-%02d\n", info.LastUsed.Year, info.LastUsed.Month, info.LastUsed.Day)
		}
	}

	// payees
	pnames := make([]string, 0, len(a.Payees))
	for name := range a.Payees {
		pnames = append(pnames, name)
	}
	sort.Strings(pnames)

	fmt.Fprintf(w, "\npayees (%d):\n", len(a.Payees))
	for _, name := range pnames {
		info := a.Payees[name]
		fmt.Fprintf(w, "  %s\n", name)
		fmt.Fprintf(w, "    directives: %d\n", len(info.Directives))
		fmt.Fprintf(w, "    used: %d\n", info.UsedCount)
		for _, d := range info.Directives {
			if d.Name != nil {
				fmt.Fprintf(w, "      payee %s\n", d.Name.Name)
			}
		}
		fmt.Fprintf(w, "    usages: %d\n", len(info.Usage))
		for _, u := range info.Usage {
			fmt.Fprintf(w, "      file %d: %s\n", u.FileIndex, u.Payee.Name)
		}
		if info.LastUsed.Year != 0 {
			fmt.Fprintf(w, "    last-used: %d-%02d-%02d\n", info.LastUsed.Year, info.LastUsed.Month, info.LastUsed.Day)
		}
	}

	// prefix index
	prefixes := make([]string, 0, len(a.AccountsByPrefix))
	for p := range a.AccountsByPrefix {
		prefixes = append(prefixes, p)
	}
	sort.Strings(prefixes)
	fmt.Fprintf(w, "\nprefixes (%d):\n", len(a.AccountsByPrefix))
	for _, p := range prefixes {
		names := a.AccountsByPrefix[p]
		sort.Strings(names)
		fmt.Fprintf(w, "  %s\n", p)
		for _, n := range names {
			fmt.Fprintf(w, "    %s\n", n)
		}
	}

	// typed entry counts
	fmt.Fprintf(w, "\ntransactions: %d\n", len(a.Transactions))
	fmt.Fprintf(w, "periodic transactions: %d\n", len(a.PeriodicTransactions))
	fmt.Fprintf(w, "automated transactions: %d\n", len(a.AutomatedTransactions))

	// directives
	fmt.Fprintf(w, "\ndirectives (%d):\n", len(a.Directives))
	for _, d := range a.Directives {
		switch d := d.(type) {
		case *ast.AccountDirective:
			fmt.Fprintf(w, "  account %s\n", d.Account.String())
		case *ast.CommodityDirective:
			fmt.Fprintf(w, "  commodity %s\n", d.Commodity)
		case *ast.PayeeDirective:
			if d.Name != nil {
				fmt.Fprintf(w, "  payee %s\n", d.Name.Name)
			}
		case *ast.TagDirective:
			fmt.Fprintf(w, "  tag %s\n", d.Name)
		case *ast.IncludeDirective:
			fmt.Fprintf(w, "  include %s\n", d.Path)
		case *ast.AliasDirective:
			fmt.Fprintf(w, "  alias %s = %s\n", d.From.String(), d.To.String())
		case *ast.YearDirective:
			fmt.Fprintf(w, "  year %d\n", d.Year)
		case *ast.DecimalMarkDirective:
			fmt.Fprintf(w, "  decimal-mark %c\n", d.Mark)
		case *ast.DefaultCommodityDirective:
			fmt.Fprintf(w, "  D %s%s\n", d.Amount.Commodity, d.Amount.Quantity.String())
		case *ast.MarketPriceDirective:
			fmt.Fprintf(w, "  P %s %s\n", d.Commodity, d.Amount.Quantity.String())
		case *ast.ConversionDirective:
			fmt.Fprintf(w, "  C %s%s @@ %s%s\n", d.From.Commodity, d.From.Quantity.String(), d.To.Commodity, d.To.Quantity.String())
		case *ast.ApplyDirective:
			fmt.Fprintf(w, "  apply %s\n", d.Expr)
		case *ast.EndDirective:
			fmt.Fprintf(w, "  end %s\n", d.Expr)
		case *ast.CommentBlockDirective:
			fmt.Fprintf(w, "  comment\n")
		case *ast.IgnoredDirective:
			fmt.Fprintf(w, "  N %s\n", d.Text)
		}
	}

	// transaction duplicate keys
	keys := make([]string, 0, len(a.TransactionsByKey))
	for k := range a.TransactionsByKey {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	fmt.Fprintf(w, "\ntransaction keys (%d):\n", len(a.TransactionsByKey))
	for _, k := range keys {
		txs := a.TransactionsByKey[k]
		if len(txs) > 1 {
			fmt.Fprintf(w, "  %s (dup: %d)\n", k, len(txs))
		} else {
			fmt.Fprintf(w, "  %s\n", k)
		}
	}

	// payee templates
	payeeNames := make([]string, 0, len(a.PayeeTemplates))
	for name := range a.PayeeTemplates {
		payeeNames = append(payeeNames, name)
	}
	sort.Strings(payeeNames)
	fmt.Fprintf(w, "\npayee templates (%d):\n", len(a.PayeeTemplates))
	for _, name := range payeeNames {
		templates := a.PayeeTemplates[name]
		fmt.Fprintf(w, "  %s\n", name)
		for _, t := range templates {
			fmt.Fprintf(w, "    %s", t.Account)
			if t.Amount != "" {
				fmt.Fprintf(w, "  %s%s", t.Commodity, t.Amount)
			}
			if t.IsInferred {
				fmt.Fprintf(w, " (inferred)")
			}
			fmt.Fprintln(w)
		}
	}

	// dates
	fmt.Fprintf(w, "\ndates (%d):\n", len(a.Dates))
	for _, d := range a.Dates {
		fmt.Fprintf(w, "  %d-%02d-%02d\n", d.Year, d.Month, d.Day)
	}

	// tags
	tnames := make([]string, 0, len(a.Tags))
	for name := range a.Tags {
		tnames = append(tnames, name)
	}
	sort.Strings(tnames)

	fmt.Fprintf(w, "\ntags (%d):\n", len(a.Tags))
	for _, name := range tnames {
		info := a.Tags[name]
		fmt.Fprintf(w, "  %s\n", name)
		fmt.Fprintf(w, "    directives: %d\n", len(info.Directives))
		fmt.Fprintf(w, "    used: %d\n", info.UsedCount)
		for _, d := range info.Directives {
			fmt.Fprintf(w, "      tag %s\n", d.Name)
		}
		fmt.Fprintf(w, "    usages: %d\n", len(info.Usage))
		for _, u := range info.Usage {
			fmt.Fprintf(w, "      file %d: %s=%q\n", u.FileIndex, u.Tag.Key, u.Tag.Value)
		}
		if len(info.Values) > 0 {
			fmt.Fprintf(w, "    values: %s\n", strings.Join(info.Values, " "))
		}
		if info.LastUsed.Year != 0 {
			fmt.Fprintf(w, "    last-used: %d-%02d-%02d\n", info.LastUsed.Year, info.LastUsed.Month, info.LastUsed.Day)
		}
	}

	// tag names
	fmt.Fprintf(w, "\ntag names (%d):\n", len(a.TagNames))
	for _, t := range a.TagNames {
		fmt.Fprintf(w, "  %s\n", t)
	}

	// tag values
	fmt.Fprintf(w, "\ntag values (%d):\n", len(a.TagValues))
	for _, v := range a.TagValues {
		fmt.Fprintf(w, "  %s\n", v)
	}

	// commodity decimal marks
	csyms2 := make([]string, 0, len(a.CommodityDecimalMarks))
	for sym := range a.CommodityDecimalMarks {
		csyms2 = append(csyms2, sym)
	}
	sort.Strings(csyms2)
	fmt.Fprintf(w, "\ncommodity decimal marks (%d):\n", len(a.CommodityDecimalMarks))
	for _, sym := range csyms2 {
		fmt.Fprintf(w, "  %s: %c\n", sym, a.CommodityDecimalMarks[sym])
	}
}
