package server

import (
	"strings"
	"testing"
)

func TestURLToModuleName(t *testing.T) {
	tests := []struct {
		url  string
		want string
	}{
		{"/web/static/src/core/foo.js", "@web/core/foo"},
		{"/web/static/src/env.js", "@web/env"},
		{"/web/static/src/session.js", "@web/session"},
		{"/stock/static/src/widgets/foo.js", "@stock/widgets/foo"},
		{"/web/static/lib/owl/owl.js", "@web/../lib/owl/owl"},
		{"/web/static/src/core/browser/browser.js", "@web/core/browser/browser"},
	}

	for _, tt := range tests {
		t.Run(tt.url, func(t *testing.T) {
			got := URLToModuleName(tt.url)
			if got != tt.want {
				t.Errorf("URLToModuleName(%q) = %q, want %q", tt.url, got, tt.want)
			}
		})
	}
}

func TestIsOdooModule(t *testing.T) {
	tests := []struct {
		name    string
		url     string
		content string
		want    bool
	}{
		{
			name:    "has import",
			url:     "/web/static/src/foo.js",
			content: `import { Foo } from "@web/bar";`,
			want:    true,
		},
		{
			name:    "has export",
			url:     "/web/static/src/foo.js",
			content: `export class Foo {}`,
			want:    true,
		},
		{
			name:    "has odoo-module tag",
			url:     "/web/static/src/foo.js",
			content: "// @odoo-module\nconst x = 1;",
			want:    true,
		},
		{
			name:    "ignore directive",
			url:     "/web/static/src/foo.js",
			content: "// @odoo-module ignore\nimport { X } from '@web/foo';",
			want:    false,
		},
		{
			name:    "plain JS no module",
			url:     "/web/static/src/foo.js",
			content: "var x = 1;\nconsole.log(x);",
			want:    false,
		},
		{
			name:    "not a JS file",
			url:     "/web/static/src/foo.xml",
			content: `import { Foo } from "@web/bar";`,
			want:    false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsOdooModule(tt.url, tt.content)
			if got != tt.want {
				t.Errorf("IsOdooModule(%q, ...) = %v, want %v", tt.url, got, tt.want)
			}
		})
	}
}

func TestExtractImports(t *testing.T) {
	t.Run("named imports", func(t *testing.T) {
		content := `import { Foo, Bar } from "@web/core/foo";
import { Baz as Qux } from "@web/core/baz";
const x = 1;`
		deps, requires, clean := extractImports(content)

		if len(deps) != 2 {
			t.Fatalf("expected 2 deps, got %d: %v", len(deps), deps)
		}
		if deps[0] != "@web/core/foo" {
			t.Errorf("deps[0] = %q, want @web/core/foo", deps[0])
		}
		if deps[1] != "@web/core/baz" {
			t.Errorf("deps[1] = %q, want @web/core/baz", deps[1])
		}

		if len(requires) != 2 {
			t.Fatalf("expected 2 requires, got %d", len(requires))
		}
		if !strings.Contains(requires[0], `{ Foo, Bar }`) {
			t.Errorf("requires[0] = %q, want Foo, Bar destructuring", requires[0])
		}
		if !strings.Contains(requires[1], `Baz: Qux`) {
			t.Errorf("requires[1] = %q, want Baz: Qux alias", requires[1])
		}

		if strings.Contains(clean, "import") {
			t.Errorf("clean content still contains import statements: %s", clean)
		}
		if !strings.Contains(clean, "const x = 1;") {
			t.Errorf("clean content should still have 'const x = 1;': %s", clean)
		}
	})

	t.Run("default import", func(t *testing.T) {
		content := `import Foo from "@web/core/foo";`
		deps, requires, _ := extractImports(content)

		if len(deps) != 1 || deps[0] != "@web/core/foo" {
			t.Errorf("deps = %v, want [@web/core/foo]", deps)
		}
		if len(requires) != 1 || !strings.Contains(requires[0], `Symbol.for("default")`) {
			t.Errorf("requires = %v, want default symbol access", requires)
		}
	})

	t.Run("namespace import", func(t *testing.T) {
		content := `import * as utils from "@web/core/utils";`
		deps, requires, _ := extractImports(content)

		if len(deps) != 1 || deps[0] != "@web/core/utils" {
			t.Errorf("deps = %v, want [@web/core/utils]", deps)
		}
		if len(requires) != 1 || !strings.Contains(requires[0], `const utils = require("@web/core/utils")`) {
			t.Errorf("requires = %v, want namespace require", requires)
		}
	})

	t.Run("side-effect import", func(t *testing.T) {
		content := `import "@web/core/setup";`
		deps, requires, _ := extractImports(content)

		if len(deps) != 1 || deps[0] != "@web/core/setup" {
			t.Errorf("deps = %v, want [@web/core/setup]", deps)
		}
		if len(requires) != 1 || requires[0] != `require("@web/core/setup");` {
			t.Errorf("requires = %v, want side-effect require", requires)
		}
	})

	t.Run("dedup deps", func(t *testing.T) {
		content := `import { Foo } from "@web/core/foo";
import { Bar } from "@web/core/foo";`
		deps, _, _ := extractImports(content)

		if len(deps) != 1 {
			t.Errorf("expected deduped deps, got %v", deps)
		}
	})
}

func TestTransformExports(t *testing.T) {
	t.Run("export class", func(t *testing.T) {
		got := transformExports("export class Foo extends Bar {")
		want := "const Foo = __exports.Foo = class Foo extends Bar {"
		if got != want {
			t.Errorf("got %q, want %q", got, want)
		}
	})

	t.Run("export function", func(t *testing.T) {
		got := transformExports("export function doSomething(a, b) {")
		want := `__exports.doSomething = function doSomething(a, b) {`
		if got != want {
			t.Errorf("got %q, want %q", got, want)
		}
	})

	t.Run("export const", func(t *testing.T) {
		got := transformExports("export const MAX_SIZE = 100;")
		want := "const MAX_SIZE = __exports.MAX_SIZE = 100;"
		if got != want {
			t.Errorf("got %q, want %q", got, want)
		}
	})

	t.Run("export let", func(t *testing.T) {
		got := transformExports("export let counter = 0;")
		want := "let counter = __exports.counter = 0;"
		if got != want {
			t.Errorf("got %q, want %q", got, want)
		}
	})

	t.Run("export default", func(t *testing.T) {
		got := transformExports("export default Foo;")
		want := `__exports[Symbol.for("default")] = Foo;`
		if got != want {
			t.Errorf("got %q, want %q", got, want)
		}
	})

	t.Run("export named", func(t *testing.T) {
		got := transformExports("export { Foo, Bar };")
		if !strings.Contains(got, "__exports.Foo = Foo;") {
			t.Errorf("missing Foo export in: %s", got)
		}
		if !strings.Contains(got, "__exports.Bar = Bar;") {
			t.Errorf("missing Bar export in: %s", got)
		}
	})

	t.Run("export named with alias", func(t *testing.T) {
		got := transformExports("export { Foo as default };")
		if !strings.Contains(got, "__exports.default = Foo;") {
			t.Errorf("missing aliased export in: %s", got)
		}
	})
}

func TestTranspileJS(t *testing.T) {
	t.Run("full transpile", func(t *testing.T) {
		content := `// @odoo-module
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";

export class MyWidget extends Component {
    static template = "web.MyWidget";
}

registry.category("actions").add("my_widget", MyWidget);
`
		url := "/web/static/src/views/my_widget.js"
		result := TranspileJS(url, content)

		// Check wrapper
		if !strings.HasPrefix(result, `odoo.define("@web/views/my_widget"`) {
			t.Errorf("missing odoo.define header: %s", result[:80])
		}

		// Check deps
		if !strings.Contains(result, `"@odoo/owl"`) {
			t.Errorf("missing @odoo/owl dependency")
		}
		if !strings.Contains(result, `"@web/core/registry"`) {
			t.Errorf("missing @web/core/registry dependency")
		}

		// Check require lines
		if !strings.Contains(result, `const { Component } = require("@odoo/owl");`) {
			t.Errorf("missing Component require")
		}
		if !strings.Contains(result, `const { registry } = require("@web/core/registry");`) {
			t.Errorf("missing registry require")
		}

		// Check export transform
		if !strings.Contains(result, `const MyWidget = __exports.MyWidget = class MyWidget`) {
			t.Errorf("missing class export transform")
		}

		// Check no raw import/export left
		if strings.Contains(result, "import {") {
			t.Errorf("raw import statement still present")
		}

		// Check wrapper close
		if !strings.Contains(result, "return __exports;") {
			t.Errorf("missing return __exports")
		}
	})

	t.Run("non-module passthrough", func(t *testing.T) {
		content := "var x = 1;\nconsole.log(x);"
		result := TranspileJS("/web/static/lib/foo.js", content)
		if result != content {
			t.Errorf("non-module content was modified")
		}
	})

	t.Run("ignore directive passthrough", func(t *testing.T) {
		content := "// @odoo-module ignore\nimport { X } from '@web/foo';\nexport class Y {}"
		result := TranspileJS("/web/static/src/foo.js", content)
		if result != content {
			t.Errorf("ignored module content was modified")
		}
	})
}

func TestParseImportSpecifiers(t *testing.T) {
	tests := []struct {
		raw  string
		want []importSpecifier
	}{
		{"Foo, Bar", []importSpecifier{{name: "Foo"}, {name: "Bar"}}},
		{"Foo as F, Bar", []importSpecifier{{name: "Foo", alias: "F"}, {name: "Bar"}}},
		{" X , Y , Z ", []importSpecifier{{name: "X"}, {name: "Y"}, {name: "Z"}}},
		{"", nil},
	}

	for _, tt := range tests {
		t.Run(tt.raw, func(t *testing.T) {
			got := parseImportSpecifiers(tt.raw)
			if len(got) != len(tt.want) {
				t.Fatalf("got %d specifiers, want %d", len(got), len(tt.want))
			}
			for i, s := range got {
				if s.name != tt.want[i].name || s.alias != tt.want[i].alias {
					t.Errorf("specifier[%d] = %+v, want %+v", i, s, tt.want[i])
				}
			}
		})
	}
}
