package ui

import (
	"bytes"
	"context"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/a-h/templ"
)

func TestButtonRendersDefaultSolidMediumMarkup(t *testing.T) {
	component := Button(ButtonProps{
		Label:   "Nouveau projet",
		Variant: ButtonVariantDefault,
		Size:    SizeMD,
		Type:    "button",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`type="button"`,
		`Nouveau projet`,
		`ui-button`,
		`ui-button-solid`,
		`ui-button-default`,
		`ui-button-md`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestIconButtonRendersBorderlessDestructiveMarkup(t *testing.T) {
	component := IconButton(IconButtonProps{
		Label:   "Supprimer le projet",
		Icon:    "trash",
		Variant: IconButtonVariantDanger,
		Tone:    IconButtonToneGhost,
		Type:    "button",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`type="button"`,
		`aria-label="Supprimer le projet"`,
		`borderless-icon-button`,
		`ui-icon-button-ghost`,
		`ui-icon-button-danger`,
		`lucide-trash2`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestIconButtonRendersBorderlessNeutralMarkup(t *testing.T) {
	component := IconButton(IconButtonProps{
		Label:   "Modifier le projet",
		Icon:    "pencil",
		Variant: IconButtonVariantNeutral,
		Tone:    IconButtonToneGhost,
		Type:    "button",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`type="button"`,
		`aria-label="Modifier le projet"`,
		`borderless-icon-button`,
		`ui-icon-button-ghost`,
		`ui-icon-button-neutral`,
		`M12 20h9`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestBadgeRendersSemanticStatusVariant(t *testing.T) {
	component := Badge(BadgeProps{
		Label:   "En cours",
		Variant: BadgeVariantWarning,
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-badge`,
		`ui-badge-warning`,
		`En cours`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSelectRendersSingleSelectMarkup(t *testing.T) {
	component := Select(SelectProps{
		ID:          "project-status",
		Name:        "status",
		Placeholder: "Select a status",
		Value:       "in-progress",
		Options: []SelectOption{
			{Value: "todo", Label: "To do"},
			{Value: "in-progress", Label: "In progress"},
			{Value: "done", Label: "Done"},
		},
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`class="ui-select"`,
		`id="project-status"`,
		`name="status"`,
		`class="ui-select-native"`,
		`class="ui-select-control"`,
		`class="ui-select-value-wrapper"`,
		`class="ui-select-arrow-zone"`,
		`class="ui-select-arrow-icon"`,
		`value="in-progress" selected`,
		`data-ui-select-root`,
		`data-ui-select-label`,
		`Select a status`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSelectRendersMultiSelectMarkup(t *testing.T) {
	component := Select(SelectProps{
		Name:        "assignee_ids",
		Placeholder: "Select multiple values",
		Multiple:    true,
		Values:      []string{"u_1", "u_2"},
		Options: []SelectOption{
			{Value: "u_1", Label: "Alice"},
			{Value: "u_2", Label: "Bob"},
			{Value: "u_3", Label: "Charlie"},
		},
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`multiple`,
		`data-ui-select-multiple="true"`,
		`data-placeholder="Select multiple values"`,
		`data-selected-label="Alice, Bob"`,
		`value="u_1" selected`,
		`value="u_2" selected`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestModalRendersShellStructure(t *testing.T) {
	component := Modal(ModalProps{
		Title:   "Nouveau projet",
		Body:    textComponent("Body copy"),
		Actions: textComponent("Actions"),
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-modal-backdrop`,
		`ui-modal-panel`,
		`Nouveau projet`,
		`Body copy`,
		`Actions`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSpaceXRendersDefaultMediumMarkup(t *testing.T) {
	component := SpaceX(SpaceProps{})

	html := renderToString(t, component)

	for _, want := range []string{
		`aria-hidden="true"`,
		`ui-space-x`,
		`ui-space-x-md`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSpaceYRendersExplicitExtraLargeMarkup(t *testing.T) {
	component := SpaceY(SpaceProps{Size: SpacingStepXL})

	html := renderToString(t, component)

	for _, want := range []string{
		`aria-hidden="true"`,
		`ui-space-y`,
		`ui-space-y-xl`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestButtonUsesSharedTokenBackedClasses(t *testing.T) {
	component := Button(ButtonProps{
		Label:   "Create",
		Variant: ButtonVariantDefault,
		Size:    SizeSM,
		Type:    "button",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-button`,
		`ui-button-solid`,
		`ui-button-default`,
		`ui-button-sm`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSharedSemanticClassesExistInStylesheet(t *testing.T) {
	cssPath := filepath.Join("..", "..", "..", "static", "styles.css")
	body, err := os.ReadFile(cssPath)
	if err != nil {
		t.Fatalf("read stylesheet: %v", err)
	}

	css := string(body)
	for _, want := range []string{
		`Code generated by cmd/buildstyles`,
		`--color-text-primary`,
		`--color-surface-default`,
		`--color-status-warning-soft-bg`,
		`--shadow-surface-md`,
		`--overlay-backdrop-default`,
		`.ui-button-solid.ui-button-default`,
		`.ui-badge-warning`,
		`.ui-card`,
		`.catalog-page`,
		`.ui-button-sm`,
		`.ui-modal-panel`,
		`.ui-space-x-md`,
		`.ui-space-y-md`,
		`.borderless-icon-button`,
		`.ui-select-control`,
		`.ui-select-native`,
		`.ui-select-chip`,
		`.ui-icon-button-solid.ui-icon-button-neutral`,
		`.ui-icon-button-ghost.ui-icon-button-neutral`,
		`.ui-icon-button-ghost.ui-icon-button-danger`,
		`.ui-button-soft.ui-button-danger`,
	} {
		if !strings.Contains(css, want) {
			t.Fatalf("expected stylesheet to contain %q", want)
		}
	}
}

func TestButtonRendersDangerLargeMarkup(t *testing.T) {
	component := Button(ButtonProps{
		Label:   "Supprimer",
		Variant: ButtonVariantDanger,
		Tone:    ButtonToneSoft,
		Size:    SizeLG,
		Type:    "submit",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`type="submit"`,
		`ui-button-soft`,
		`ui-button-danger`,
		`ui-button-lg`,
		`Supprimer`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestButtonRendersSoftWarningMarkup(t *testing.T) {
	component := Button(ButtonProps{
		Label:   "Relancer",
		Variant: ButtonVariantWarning,
		Tone:    ButtonToneSoft,
		Size:    SizeMD,
		Type:    "button",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`type="button"`,
		`ui-button-soft`,
		`ui-button-warning`,
		`ui-button-md`,
		`Relancer`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestInputRendersSharedControlMarkup(t *testing.T) {
	component := Input(InputProps{
		Name:        "name",
		Value:       "My project",
		Placeholder: "Nom du projet",
		Type:        "text",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`name="name"`,
		`value="My project"`,
		`placeholder="Nom du projet"`,
		`class="ui-input"`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestTextareaRendersSharedControlMarkup(t *testing.T) {
	component := Textarea(TextareaProps{
		Name:        "description",
		Value:       "Longer copy",
		Placeholder: "Description",
		Rows:        4,
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`name="description"`,
		`placeholder="Description"`,
		`rows="4"`,
		`class="ui-textarea"`,
		`Longer copy`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestFormFieldRendersLabelAndError(t *testing.T) {
	component := FormField(FormFieldProps{
		Label: "Nom",
		For:   "tablo-name",
		Field: Input(InputProps{Name: "name", Type: "text"}),
		Error: "Le nom est requis",
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-form-field`,
		`for="tablo-name"`,
		`Nom`,
		`ui-form-error`,
		`Le nom est requis`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestCardRendersSharedRegions(t *testing.T) {
	component := Card(CardProps{
		Header: textComponent("Header"),
		Body:   textComponent("Body"),
		Footer: textComponent("Footer"),
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-card`,
		`ui-card-header`,
		`ui-card-body`,
		`ui-card-footer`,
		`Body`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestTableRendersSharedShell(t *testing.T) {
	component := Table(TableProps{
		Head: textComponent("<tr><th>Projet</th></tr>"),
		Body: textComponent("<tr><td>Hello</td></tr>"),
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-table-shell`,
		`class="ui-table"`,
		`<thead>`,
		`<tbody>`,
		`Projet`,
		`Hello`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestEmptyStateRendersTitleDescriptionAndAction(t *testing.T) {
	component := EmptyState(EmptyStateProps{
		Title:       "Aucun projet",
		Description: "Créez votre premier projet.",
		Action:      textComponent("Créer"),
	})

	html := renderToString(t, component)

	for _, want := range []string{
		`ui-empty-state`,
		`Aucun projet`,
		`Créez votre premier projet.`,
		`Créer`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func renderToString(t *testing.T, component templ.Component) string {
	t.Helper()

	var buf bytes.Buffer
	if err := component.Render(context.Background(), &buf); err != nil {
		t.Fatalf("render component: %v", err)
	}
	return buf.String()
}

func textComponent(text string) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		_, err := w.Write([]byte(text))
		return err
	})
}
