"""Tests for textual_wtf.forms — metaclass, binding, embed, validate, clean."""

import pytest

from textual_wtf.exceptions import AmbiguousFieldError, FormError, ValidationError
from textual_wtf.fields import IntegerField, StringField, TextField
from textual_wtf.forms import BaseForm, Form


# ── Test forms ──────────────────────────────────────────────────


class SimpleForm(Form):
    name = StringField("Name", required=True)
    age = IntegerField("Age", minimum=0, maximum=150)


class AddressForm(Form):
    street = StringField("Street")
    city = StringField("City")
    postcode = StringField("Postcode")


class OrderForm(Form):
    """Uses direct Form-subclass assignment (the only embedding mechanism)."""
    billing = AddressForm
    shipping = AddressForm
    notes = TextField("Notes")


class TitledForm(Form):
    title = "My Form"
    name = StringField("Name")


# ── Metaclass ───────────────────────────────────────────────────


class TestFormMetaclass:
    def test_fields_extracted(self):
        defs = SimpleForm._field_definitions
        assert "name" in defs
        assert "age" in defs
        assert len(defs) == 2

    def test_field_order_preserved(self):
        assert list(SimpleForm._field_definitions.keys()) == ["name", "age"]

    def test_fields_not_left_as_class_attrs(self):
        assert not isinstance(getattr(SimpleForm, "name", None), StringField)

    def test_embedded_expansion(self):
        defs = OrderForm._field_definitions
        assert "billing_street" in defs
        assert "billing_city" in defs
        assert "billing_postcode" in defs
        assert "shipping_street" in defs
        assert "shipping_city" in defs
        assert "shipping_postcode" in defs
        assert "notes" in defs
        assert len(defs) == 7

    def test_embedded_name_collision_raises(self):
        with pytest.raises(FormError, match="conflicts"):

            class BadForm(Form):
                billing_street = StringField("Street")
                billing = AddressForm

    def test_direct_class_assignment_expands_fields(self):
        class DirectOrderForm(Form):
            billing = AddressForm
            shipping = AddressForm
            notes = TextField("Notes")

        defs = DirectOrderForm._field_definitions
        assert "billing_street" in defs
        assert "billing_city" in defs
        assert "billing_postcode" in defs
        assert "shipping_street" in defs
        assert "shipping_city" in defs
        assert "shipping_postcode" in defs
        assert "notes" in defs
        assert len(defs) == 7

    def test_direct_class_assignment_removes_class_attr(self):
        class DirectOrderForm(Form):
            billing = AddressForm

        assert not isinstance(getattr(DirectOrderForm, "billing", None), type)

    def test_direct_class_assignment_collision_raises(self):
        with pytest.raises(FormError, match="conflicts"):

            class BadForm(Form):
                billing_street = StringField("Street")
                billing = AddressForm

    def test_direct_class_assignment_get_data(self):
        class DirectOrderForm(Form):
            billing = AddressForm

        form = DirectOrderForm(data={"billing_street": "99 High St"})
        assert form.get_data()["billing_street"] == "99 High St"

    def test_direct_class_assignment_unqualified_access(self):
        class SingleEmbedForm(Form):
            addr = AddressForm

        form = SingleEmbedForm()
        assert form.street.name == "addr_street"

    def test_direct_class_assignment_ambiguous_raises(self):
        class DualEmbedForm(Form):
            billing = AddressForm
            shipping = AddressForm

        with pytest.raises(AmbiguousFieldError):
            _ = DualEmbedForm().street


# ── Form instance ──────────────────────────────────────────────


class TestFormInstance:
    def test_bound_fields_created(self):
        form = SimpleForm()
        assert len(form.bound_fields) == 2
        assert "name" in form.bound_fields
        assert "age" in form.bound_fields

    def test_fields_alias(self):
        form = SimpleForm()
        assert form.fields is form.bound_fields

    def test_attribute_access(self):
        bf = SimpleForm().name
        assert bf.name == "name"
        assert bf.label == "Name"

    def test_get_field(self):
        assert SimpleForm().get_field("name").name == "name"

    def test_unknown_field_raises(self):
        with pytest.raises(AttributeError, match="no field"):
            _ = SimpleForm().nonexistent

    def test_get_field_unknown_raises(self):
        with pytest.raises(AttributeError, match="no field"):
            SimpleForm().get_field("nonexistent")

    def test_initial_data(self):
        form = SimpleForm(data={"name": "Alice", "age": "30"})
        assert form.name.value == "Alice"
        assert form.age.value == 30

    def test_missing_data_uses_defaults(self):
        form = SimpleForm(data={"name": "Alice"})
        assert form.age.value is None


# ── Data access ─────────────────────────────────────────────────


class TestFormDataAccess:
    def test_get_data(self):
        form = SimpleForm(data={"name": "Alice", "age": "30"})
        data = form.get_data()
        assert data["name"] == "Alice"
        assert data["age"] == 30

    def test_set_data(self):
        form = SimpleForm()
        form.set_data({"name": "Bob", "age": "25"})
        assert form.name.value == "Bob"
        assert form.age.value == 25

    def test_set_data_ignores_unknown(self):
        form = SimpleForm()
        form.set_data({"name": "Bob", "unknown": "val"})
        assert form.name.value == "Bob"


# ── Validation ──────────────────────────────────────────────────


class TestFormValidation:
    def test_all_valid(self):
        form = SimpleForm(data={"name": "Alice", "age": "30"})
        assert form.validate() is True

    def test_required_missing_fails(self):
        form = SimpleForm()
        assert form.validate() is False
        assert len(form.name.errors) > 0

    def test_integer_out_of_range_fails(self):
        form = SimpleForm(data={"name": "Alice", "age": "200"})
        assert form.validate() is False
        assert len(form.age.errors) > 0

    def test_is_valid_alias(self):
        form = SimpleForm(data={"name": "Alice"})
        assert form.is_valid() is True

    def test_populates_errors(self):
        form = SimpleForm()
        form.validate()
        assert form.name.has_error is True
        assert len(form.name.error_messages) > 0

    def test_clears_errors_on_pass(self):
        form = SimpleForm(data={"name": "Alice"})
        form.validate()
        assert form.name.has_error is False
        assert form.name.errors == []


# ── Clean ───────────────────────────────────────────────────────


class TestFormClean:
    def test_clean_calls_validate(self):
        form = SimpleForm(data={"name": "Alice"})
        assert form.clean() is True

    def test_clean_fails_when_validate_fails(self):
        form = SimpleForm()  # name required but missing
        assert form.clean() is False

    def test_clean_form_hook_called_on_success(self):
        """clean_form() is called after validate() passes."""
        called = []

        class HookForm(Form):
            name = StringField("Name", required=True)

            def clean_form(self):
                called.append(True)
                return True

        form = HookForm(data={"name": "Alice"})
        form.clean()
        assert called == [True]

    def test_clean_form_hook_not_called_on_failure(self):
        """clean_form() is NOT called when validate() fails."""
        called = []

        class HookForm(Form):
            name = StringField("Name", required=True)

            def clean_form(self):
                called.append(True)
                return True

        form = HookForm()
        form.clean()
        assert called == []

    def test_clean_form_can_reject(self):
        """clean_form() returning False means clean() returns False."""

        class CrossFieldForm(Form):
            password = StringField("Password", required=True)
            confirm = StringField("Confirm", required=True)

            def clean_form(self):
                if self.password.value != self.confirm.value:
                    self.add_error("confirm", "Passwords do not match")
                    return False
                return True

        form = CrossFieldForm(data={"password": "abc", "confirm": "xyz"})
        assert form.clean() is False
        assert "Passwords do not match" in form.confirm.errors

    def test_clean_form_cross_field_pass(self):
        class CrossFieldForm(Form):
            password = StringField("Password", required=True)
            confirm = StringField("Confirm", required=True)

            def clean_form(self):
                if self.password.value != self.confirm.value:
                    return False
                return True

        form = CrossFieldForm(data={"password": "abc", "confirm": "abc"})
        assert form.clean() is True


# ── add_error ───────────────────────────────────────────────────


class TestAddError:
    def test_add_error_marks_field(self):
        class CrossFieldForm(Form):
            password = StringField("Password", required=True)
            confirm = StringField("Confirm", required=True)

            def clean_form(self):
                if self.password.value != self.confirm.value:
                    self.add_error("confirm", "Passwords do not match.")
                return True  # still returns True; add_error sets the flag

        form = CrossFieldForm(data={"password": "abc", "confirm": "xyz"})
        assert form.clean() is False
        assert form.confirm.has_error is True
        assert "Passwords do not match." in form.confirm.errors

    def test_add_error_unknown_field_raises(self):
        form = SimpleForm(data={"name": "Alice"})
        with pytest.raises(FormError, match="no field"):
            form.add_error("nonexistent", "oops")

    def test_add_error_multiple_messages(self):
        class MultiErrorForm(Form):
            x = StringField("X", required=True)

            def clean_form(self):
                self.add_error("x", "First problem.")
                self.add_error("x", "Second problem.")
                return True

        form = MultiErrorForm(data={"x": "hello"})
        assert form.clean() is False
        assert len(form.x.errors) == 2

    def test_add_error_clean_returns_false_even_if_clean_form_returns_true(self):
        class F(Form):
            name = StringField("Name", required=True)

            def clean_form(self):
                self.add_error("name", "Nope.")
                return True

        form = F(data={"name": "Alice"})
        assert form.clean() is False


# ── Embedded form access ────────────────────────────────────────


class TestEmbeddedFormAccess:
    def test_qualified_access(self):
        bf = OrderForm().billing_street
        assert bf.name == "billing_street"

    def test_unqualified_unambiguous(self):
        assert OrderForm().notes.name == "notes"

    def test_unqualified_ambiguous_raises(self):
        with pytest.raises(AmbiguousFieldError) as exc_info:
            _ = OrderForm().street
        assert "street" in exc_info.value.name
        assert len(exc_info.value.candidates) == 2

    def test_embedded_get_data(self):
        form = OrderForm(data={"billing_street": "123 Main", "shipping_city": "London"})
        data = form.get_data()
        assert data["billing_street"] == "123 Main"
        assert data["shipping_city"] == "London"

    def test_embedded_set_data(self):
        form = OrderForm()
        form.set_data({"billing_street": "456 Oak"})
        assert form.billing_street.value == "456 Oak"


# ── Inheritance ─────────────────────────────────────────────────


class TestFormInheritance:
    def test_subclass_inherits_fields(self):

        class ExtendedForm(SimpleForm):
            email = StringField("Email")

        defs = ExtendedForm._field_definitions
        assert "name" in defs
        assert "age" in defs
        assert "email" in defs
        assert len(defs) == 3

    def test_subclass_field_order(self):

        class ExtendedForm(SimpleForm):
            email = StringField("Email")

        assert list(ExtendedForm._field_definitions.keys()) == [
            "name",
            "age",
            "email",
        ]


# ── Title and layout class ─────────────────────────────────────


class TestFormTitle:
    def test_title(self):
        assert TitledForm().title == "My Form"

    def test_no_title(self):
        assert SimpleForm().title == ""

    def test_title_kwarg_overrides_class_attr(self):
        assert TitledForm(title="Override").title == "Override"

    def test_title_kwarg_on_plain_form(self):
        assert SimpleForm(title="Hello").title == "Hello"


class TestFormLayoutClass:
    def test_default_none(self):
        assert SimpleForm.layout_class is None

    def test_custom(self):
        from textual_wtf.layouts import FormLayout

        class CustomForm(Form):
            layout_class = FormLayout
            name = StringField("Name")

        assert CustomForm.layout_class is FormLayout

    def test_layout_callable_dispatched(self):
        """layout() with a callable calls it and returns the result directly."""
        from unittest.mock import MagicMock
        sentinel = MagicMock()
        received = []

        def my_layout(form):
            received.append(form)
            return sentinel

        form = SimpleForm()
        result = form.layout(my_layout)
        assert received == [form]
        assert result is sentinel


# ── Label style cascade ────────────────────────────────────────


class TestLabelStyleCascade:
    def test_form_level_propagates(self):

        class BesideForm(Form):
            label_style = "beside"
            name = StringField("Name")
            email = StringField("Email")

        form = BesideForm()
        assert form.name.label_style == "beside"
        assert form.email.label_style == "beside"

    def test_field_level_overrides_form_level(self):

        class MixedForm(Form):
            label_style = "beside"
            name = StringField("Name", label_style="placeholder")
            email = StringField("Email")

        form = MixedForm()
        assert form.name.label_style == "placeholder"
        assert form.email.label_style == "beside"

    def test_instance_label_style_override(self):
        form = SimpleForm(label_style="beside")
        assert form.name.label_style == "beside"


# ── Help style cascade ─────────────────────────────────────────


class TestHelpStyleCascade:
    def test_form_level_propagates(self):

        class TooltipForm(Form):
            help_style = "tooltip"
            name = StringField("Name")
            email = StringField("Email")

        form = TooltipForm()
        assert form.name.help_style == "tooltip"
        assert form.email.help_style == "tooltip"

    def test_field_level_overrides_form_level(self):

        class MixedForm(Form):
            help_style = "tooltip"
            name = StringField("Name", help_style="below")
            email = StringField("Email")

        form = MixedForm()
        assert form.name.help_style == "below"
        assert form.email.help_style == "tooltip"

    def test_instance_help_style_override(self):
        form = SimpleForm(help_style="tooltip")
        assert form.name.help_style == "tooltip"

    def test_instance_overrides_class_default(self):
        """Instance kwarg beats the class-level default."""

        class BelowForm(Form):
            help_style = "below"
            name = StringField("Name")

        form = BelowForm(help_style="tooltip")
        assert form.name.help_style == "tooltip"

    def test_field_explicit_survives_instance_override(self):
        """A field with an explicit help_style is not overridden by the instance kwarg."""

        class F(Form):
            name = StringField("Name", help_style="below")
            email = StringField("Email")

        form = F(help_style="tooltip")
        assert form.name.help_style == "below"   # field set it explicitly
        assert form.email.help_style == "tooltip"  # instance default applied


# ── Required cascade — instance-level override ─────────────────


class TestRequiredCascade:
    """Form(required=...) cascades to fields that have no explicit required."""

    def test_instance_required_true_propagates(self):
        class F(Form):
            name = StringField("Name")
            age = StringField("Age")

        form = F(required=True)
        assert form.name.required is True
        assert form.age.required is True

    def test_instance_required_false_propagates(self):
        class F(Form):
            name = StringField("Name")

        form = F(required=False)
        assert form.name.required is False

    def test_field_explicit_true_wins_over_instance_false(self):
        class F(Form):
            name = StringField("Name", required=True)  # explicit
            email = StringField("Email")               # no explicit

        form = F(required=False)
        assert form.name.required is True   # field-level explicit wins
        assert form.email.required is False  # instance default applies

    def test_field_explicit_false_wins_over_instance_true(self):
        class F(Form):
            name = StringField("Name", required=False)  # explicit
            email = StringField("Email")                # no explicit

        form = F(required=True)
        assert form.name.required is False  # field-level explicit wins
        assert form.email.required is True   # instance default applies

    def test_no_override_keeps_field_defaults(self):
        class F(Form):
            name = StringField("Name", required=True)
            email = StringField("Email")

        form = F()  # no required kwarg
        assert form.name.required is True
        assert form.email.required is False

    def test_instance_required_affects_validators(self):
        """Required validator is added/removed to match the cascade result."""
        from textual_wtf.validators import Required

        class F(Form):
            name = StringField("Name")

        form_req = F(required=True)
        form_opt = F(required=False)
        assert any(isinstance(v, Required) for v in form_req.name.validators)
        assert not any(isinstance(v, Required) for v in form_opt.name.validators)

    def test_instance_required_validates_correctly(self):
        class F(Form):
            name = StringField("Name")

        form = F(required=True)
        assert form.name.validate() is False   # empty → required fails
        form.name.value = "Alice"
        assert form.name.validate() is True


# ── Embedded Form instance assignment ──────────────────────────


class TestEmbeddedFormInstance:
    """Form *instance* class-level assignments with required= overrides."""

    def _make_addr_class(self):
        """Helper: an AddressForm whose fields have no explicit required."""
        class Addr(Form):
            street = StringField("Street")
            city = StringField("City")
        return Addr

    def test_fields_expanded_with_prefix(self):
        Addr = self._make_addr_class()

        class F(Form):
            addr = Addr(required=True)

        assert "addr_street" in F._field_definitions
        assert "addr_city" in F._field_definitions

    def test_class_attr_removed(self):
        Addr = self._make_addr_class()

        class F(Form):
            addr = Addr()

        assert not isinstance(getattr(F, "addr", None), BaseForm)

    def test_required_true_cascades(self):
        Addr = self._make_addr_class()

        class F(Form):
            addr = Addr(required=True)

        form = F()
        assert form.addr_street.required is True
        assert form.addr_city.required is True

    def test_required_false_cascades(self):
        Addr = self._make_addr_class()

        class F(Form):
            addr = Addr(required=False)

        form = F()
        assert form.addr_street.required is False

    def test_different_required_per_embedding(self):
        Addr = self._make_addr_class()

        class F(Form):
            billing = Addr(required=True)
            shipping = Addr(required=False)

        form = F()
        assert form.billing_street.required is True
        assert form.billing_city.required is True
        assert form.shipping_street.required is False
        assert form.shipping_city.required is False

    def test_field_explicit_required_wins_over_embedding_override(self):
        """A field with required=True explicit survives required=False on the embedding."""
        class StrictAddr(Form):
            street = StringField("Street", required=True)  # explicit
            city = StringField("City")                     # no explicit

        class F(Form):
            addr = StrictAddr(required=False)

        form = F()
        assert form.addr_street.required is True   # explicit field-level wins
        assert form.addr_city.required is False     # override applies

    def test_class_assignment_unaffected_by_adjacent_instance(self):
        """A plain class assignment alongside an instance assignment keeps originals."""
        Addr = self._make_addr_class()

        class F(Form):
            billing = Addr            # class — no override
            shipping = Addr(required=True)  # instance — required=True

        form = F()
        assert form.billing_street.required is False  # unchanged (field default)
        assert form.shipping_street.required is True   # instance override applied

    def test_instance_embed_collision_raises(self):
        Addr = self._make_addr_class()

        with pytest.raises(FormError, match="conflicts"):
            class BadForm(Form):
                addr_street = StringField("Street")
                addr = Addr(required=True)

    def test_instance_embed_get_data(self):
        Addr = self._make_addr_class()

        class F(Form):
            addr = Addr(required=True)

        form = F(data={"addr_street": "99 High St"})
        assert form.get_data()["addr_street"] == "99 High St"

    def test_instance_embed_validation(self):
        Addr = self._make_addr_class()

        class F(Form):
            billing = Addr(required=True)
            shipping = Addr(required=False)

        form = F()  # all empty
        assert form.billing_street.validate() is False  # required → fails
        assert form.shipping_street.validate() is True  # optional → passes


# ── Required cascade — class-attribute default ────────────────


class TestRequiredClassAttr:
    """Form.required class attribute is the lowest-priority cascade level."""

    def test_class_attr_true_propagates(self):
        """Fields with no explicit required inherit from the class attribute."""
        class F(Form):
            required = True
            name = StringField("Name")
            email = StringField("Email")

        form = F()
        assert form.name.required is True
        assert form.email.required is True

    def test_class_attr_false_propagates(self):
        class F(Form):
            required = False
            name = StringField("Name")

        form = F()
        assert form.name.required is False

    def test_field_explicit_wins_over_class_attr(self):
        """An explicitly-set field required= overrides the class attribute."""
        class F(Form):
            required = False
            name = StringField("Name", required=True)   # explicit
            email = StringField("Email")                # no explicit

        form = F()
        assert form.name.required is True   # field-level explicit wins
        assert form.email.required is False  # class attr applies

    def test_instance_kwarg_beats_class_attr(self):
        """Form(required=...) kwarg overrides the class attribute."""
        class F(Form):
            required = True   # class default: required
            name = StringField("Name")

        form = F(required=False)  # instance kwarg overrides
        assert form.name.required is False

    def test_class_attr_used_when_no_instance_kwarg(self):
        """Class attr is used when the instance kwarg is absent."""
        class F(Form):
            required = True
            name = StringField("Name")

        form = F()  # no required kwarg
        assert form.name.required is True

    def test_class_attr_none_leaves_field_defaults(self):
        """Subclasses that do not set required= keep field defaults unchanged."""
        class F(Form):
            name = StringField("Name", required=True)
            email = StringField("Email")  # default False

        form = F()
        assert form.name.required is True
        assert form.email.required is False

    def test_class_attr_embedded_via_instance(self):
        """Class attr cascade travels through Form instance embedding."""
        class Addr(Form):
            required = True
            street = StringField("Street")
            city = StringField("City")

        class Order(Form):
            billing = Addr()            # inherits required=True from class attr
            shipping = Addr(required=False)  # kwarg overrides

        form = Order()
        assert form.billing_street.required is True
        assert form.billing_city.required is True
        assert form.shipping_street.required is False
        assert form.shipping_city.required is False

    def test_class_attr_validates_correctly(self):
        """Validation reflects the class-attribute cascade."""
        class F(Form):
            required = True
            name = StringField("Name")

        form = F()
        assert form.name.validate() is False   # empty → required fails
        form.name.value = "Alice"
        assert form.name.validate() is True
