package query

import (
	"slices"
	"testing"

	"mimi/internal/provider/logseq"
)

const (
	// FIXME: Simplify graph and store in the project
	graphPath = "/home/user/code/clone/cvland"
)

func TestEval(t *testing.T) {
	query2expected := map[string]int{
		`<div class="query-results"><div class="query-results-header">Query: <code>(property :supply &quot;next-month&quot;)</code></div><p><em>No results</em></p></div>`:  76,
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-property :wood-durability)</code></div><p><em>No results</em></p></div>`: 35,
		`<div class="query-results"><div class="query-results-header">Query: <code>@master</code></div><p><em>No results</em></p></div>`:                      7,
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-tags psycho)</code></div><p><em>No results</em></p></div>`:           5,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and (page-tags species) (not (page-tags class)) (and (page-tags supply)))</code></div><p><em>No results</em></p></div>`: 2,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and (page-tags genus))</code> (4 results)</div><ul>
<li><a href="/cybics/crystal/cinnamon" class="internal-link">cybics/crystal/cinnamon</a></li>
<li><a href="/cybics/crystal/curcuma" class="internal-link">cybics/crystal/curcuma</a></li>
<li><a href="/cybics/crystal/resin" class="internal-link">cybics/crystal/resin</a></li>
<li><a href="/cybics/crystal/taro" class="internal-link">cybics/crystal/taro</a></li>
</ul></div>`:             364,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and fern (page-tags species ))</code></div><p><em>No results</em></p></div>`: 14,
	}
	g := logseq.NewRegexGraph(graphPath)

	for q, expected := range query2expected {
		res, err := Eval(t.Context(), g, q)
		if err != nil {
			t.Errorf("failed to eval query '%s' with %s", q, err)
		}
		if len(res.Pages) != expected {
			titles := make([]string, len(res.Pages))
			for i, page := range res.Pages {
				titles[i] = page.Title()
			}
			t.Errorf("got %d pages instead of %d for '%s', titles: %#v", len(res.Pages), expected, q, titles)
		}
	}
}

func TestEval_Fails(t *testing.T) {
	before2expected := map[string]string{
		`<div class="query-results"><div class="query-results-header">Query: <code>(and [] (page-tags species) (not (page-tags class)))</code> (16 results)</div><ul>
<li><a href="/cyber-valley/cve/land/chayote-harvest" class="internal-link">cyber-valley/cve/land/chayote harvest</a></li>
<li><a href="/cyber-valley/cve/land/propagate-plants" class="internal-link">cyber-valley/cve/land/propagate plants</a></li>
<li><a href="/cyber-valley/cve/ops/animal-care" class="internal-link">cyber-valley/cve/ops/animal care</a></li>
<li><a href="/cyber-valley/cve/ops/avocado-harvest" class="internal-link">cyber-valley/cve/ops/avocado harvest</a></li>
<li><a href="/cyber-valley/cve/ops/banana-harvest" class="internal-link">cyber-valley/cve/ops/banana harvest</a></li>
<li><a href="/cyber-valley/cve/ops/deworming-sheeps" class="internal-link">cyber-valley/cve/ops/deworming sheeps</a></li>
<li><a href="/cyber-valley/cve/ops/feeding-guideline" class="internal-link">cyber-valley/cve/ops/feeding guideline</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-carrot" class="internal-link">cyber-valley/cve/ops/harvest carrot</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-jackfruit" class="internal-link">cyber-valley/cve/ops/harvest jackfruit</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-seeds" class="internal-link">cyber-valley/cve/ops/harvest seeds</a></li>
<li><a href="/cyberia/research/cyber-sheep/cyber-sheep" class="internal-link">cyberia/research/cyber-sheep/cyber-sheep</a></li>
<li><a href="/cybics/bio/mycelium" class="internal-link">cybics/bio/mycelium</a></li>
<li><a href="/cybics/chemo/curcuma" class="internal-link">cybics/chemo/curcuma</a></li>
<li><a href="/cybics/crystal/daun-salam" class="internal-link">cybics/crystal/daun salam</a></li>
<li><a href="/cybics/crystal/forest" class="internal-link">cybics/crystal/forest</a></li>
<li><a href="/cybics/crystal/pseudomonas-aeruginosa" class="internal-link">cybics/crystal/pseudomonas aeruginosa</a></li>
</ul></div>`: `failed to evaluate state with failed to evaluate 'and' with unexpected string atom '[]'`,
	}

	g := logseq.NewRegexGraph(graphPath)

	for before, expected := range before2expected {
		_, err := Eval(t.Context(), g, before)
		if err == nil || err.Error() != expected {
			t.Errorf("query '%s' should fail with '%s' but failed with '%s'", before, expected, err)
		}
	}
}

func TestParsing_RawEDN(t *testing.T) {
	queries := []string{
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-property :wood-durability)</code></div><p><em>No results</em></p></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>@master</code></div><p><em>No results</em></p></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-tags psycho)</code></div><p><em>No results</em></p></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and [] (page-tags species) (not (page-tags class)))</code> (16 results)</div><ul>
<li><a href="/cyber-valley/cve/land/chayote-harvest" class="internal-link">cyber-valley/cve/land/chayote harvest</a></li>
<li><a href="/cyber-valley/cve/land/propagate-plants" class="internal-link">cyber-valley/cve/land/propagate plants</a></li>
<li><a href="/cyber-valley/cve/ops/animal-care" class="internal-link">cyber-valley/cve/ops/animal care</a></li>
<li><a href="/cyber-valley/cve/ops/avocado-harvest" class="internal-link">cyber-valley/cve/ops/avocado harvest</a></li>
<li><a href="/cyber-valley/cve/ops/banana-harvest" class="internal-link">cyber-valley/cve/ops/banana harvest</a></li>
<li><a href="/cyber-valley/cve/ops/deworming-sheeps" class="internal-link">cyber-valley/cve/ops/deworming sheeps</a></li>
<li><a href="/cyber-valley/cve/ops/feeding-guideline" class="internal-link">cyber-valley/cve/ops/feeding guideline</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-carrot" class="internal-link">cyber-valley/cve/ops/harvest carrot</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-jackfruit" class="internal-link">cyber-valley/cve/ops/harvest jackfruit</a></li>
<li><a href="/cyber-valley/cve/ops/harvest-seeds" class="internal-link">cyber-valley/cve/ops/harvest seeds</a></li>
<li><a href="/cyberia/research/cyber-sheep/cyber-sheep" class="internal-link">cyberia/research/cyber-sheep/cyber-sheep</a></li>
<li><a href="/cybics/bio/mycelium" class="internal-link">cybics/bio/mycelium</a></li>
<li><a href="/cybics/chemo/curcuma" class="internal-link">cybics/chemo/curcuma</a></li>
<li><a href="/cybics/crystal/daun-salam" class="internal-link">cybics/crystal/daun salam</a></li>
<li><a href="/cybics/crystal/forest" class="internal-link">cybics/crystal/forest</a></li>
<li><a href="/cybics/crystal/pseudomonas-aeruginosa" class="internal-link">cybics/crystal/pseudomonas aeruginosa</a></li>
</ul></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and (property :supply &quot;next-month&quot;) (property :project &quot;edible oils&quot;))</code></div><p><em>No results</em></p></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and conifer (and) (page-tags genus))</code></div><p><em>No results</em></p></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(and (page-tags genus) (not (page-tags class)) (not (page-tags research)) (not (page-tags prohibited)))</code> (4 results)</div><ul>
<li><a href="/cybics/crystal/cinnamon" class="internal-link">cybics/crystal/cinnamon</a></li>
<li><a href="/cybics/crystal/curcuma" class="internal-link">cybics/crystal/curcuma</a></li>
<li><a href="/cybics/crystal/resin" class="internal-link">cybics/crystal/resin</a></li>
<li><a href="/cybics/crystal/taro" class="internal-link">cybics/crystal/taro</a></li>
</ul></div>`,
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-property :type &quot;sector&quot;)</code></div><p><em>No results</em></p></div>`,
	}
	for _, q := range queries {
		_, err := parseQuery(q)
		if err != nil {
			t.Errorf("failed query %s with %s", q, err)
		}
	}
}

func TestParsing_Options(t *testing.T) {
	query2expected := map[string]QueryOptions{
		`<div class="query-results"><div class="query-results-header">Query: <code>(page-tags super)</code></div><p><em>No results</em></p></div>
  query-properties:: [:page :tags :alias]
  query-sort-by:: page
	query-sort-desc:: true`: QueryOptions{
			properties: []string{"page", "tags", "alias"},
			sortBy:     "page",
			sortDesc:   true,
		},
	}

	for query, expected := range query2expected {
		got, err := parseQuery(query)
		if err != nil {
			t.Errorf("failed to parse query %s with %s", query, err)
		}
		if got.opts.sortBy != expected.sortBy {
			goto testFailed
		}

		if got.opts.sortDesc != expected.sortDesc {
			goto testFailed
		}

		if !slices.Equal(got.opts.properties, expected.properties) {
			goto testFailed
		}

		continue

	testFailed:
		t.Errorf("got %#v, expected %#v", got, expected)
	}
}

func TestBuildTable(t *testing.T) {
	pages := [][]logseq.Page{
		[]logseq.Page{
			logseq.Page{
				Path: "foo.md",
				Info: logseq.PageInfo{
					Props: []logseq.Property{
						logseq.Property{
							Name:   "tags",
							Values: []string{"1", "2", "3"},
							Level:  logseq.PageLevel,
						},
						logseq.Property{
							Name:   "alias",
							Values: []string{"bar"},
							Level:  logseq.PageLevel,
						},
					},
					Refs: []string{},
				},
			},
			logseq.Page{
				Path: "bar.md",
				Info: logseq.PageInfo{
					Props: []logseq.Property{
						logseq.Property{
							Name:   "tags",
							Values: []string{"baz", "huz"},
							Level:  logseq.PageLevel,
						},
					},
					Refs: []string{},
				},
			},
		},
		[]logseq.Page{
			logseq.Page{Path: "foo.md"},
			logseq.Page{Path: "bar.md"},
			logseq.Page{Path: "buz.md"},
		},
	}
	opts := []QueryOptions{
		QueryOptions{
			properties: []string{"page", "tags", "alias"},
			sortBy:     "alias",
			sortDesc:   true,
		},
		QueryOptions{
			properties: []string{"page"},
			sortBy:     "page",
			sortDesc:   false,
		},
	}
	expected := [][][]string{
		[][]string{
			[]string{"page", "tags", "alias"},
			[]string{"foo", "1, 2, 3", "bar"},
			[]string{"bar", "baz, huz", ""},
		},
		[][]string{
			[]string{"page"},
			[]string{"bar"},
			[]string{"buz"},
			[]string{"foo"},
		},
	}

	if len(pages) != len(opts) || len(opts) != len(expected) {
		t.Errorf("wrong test setup")
	}

	for i := 0; i < len(pages); i++ {
		t.Logf("Checking %#v", opts[i])
		table := buildTable(pages[i], opts[i])
		if slices.EqualFunc(table, expected[i], slices.Equal) {
			continue
		}
		t.Errorf("expected %#v, got %#v", expected[i], table)
	}
}

Graph