# retoor <retoor@molodetz.nl>

from starlette.datastructures import FormData

from devplacepy.dependencies import _formdata_to_dict, _JsonOrForm, json_or_form
from devplacepy.models import (
    PostForm,
    CommentForm,
    LoginForm,
    SignupForm,
    VoteForm,
    IssueStatusForm,
)


# ── _formdata_to_dict unit tests ────────────────────────────────────────────

def test_formdata_scalar_fields():
    """Plain string fields map directly."""
    fd = FormData([("content", "hello world"), ("title", "My Title")])
    body = _formdata_to_dict(fd, PostForm)
    assert body == {"content": "hello world", "title": "My Title"}


def test_formdata_skips_absent_scalar():
    """Fields not present in the form are omitted so Pydantic applies defaults."""
    fd = FormData([("content", "enough content here")])
    body = _formdata_to_dict(fd, PostForm)
    assert "content" in body
    assert "title" not in body
    assert "topic" not in body


def test_formdata_list_field_with_values():
    """Multi-value list fields become Python lists."""
    fd = FormData([
        ("poll_options", "Red"),
        ("poll_options", "Green"),
        ("poll_options", "Blue"),
    ])
    body = _formdata_to_dict(fd, PostForm)
    assert body["poll_options"] == ["Red", "Green", "Blue"]


def test_formdata_list_field_single_value():
    """A single value for a list field becomes a single-element list."""
    fd = FormData([("poll_options", "Only")])
    body = _formdata_to_dict(fd, PostForm)
    assert body["poll_options"] == ["Only"]


def test_formdata_list_field_absent():
    """Absent list fields are omitted."""
    fd = FormData([("content", "x" * 20)])
    body = _formdata_to_dict(fd, PostForm)
    assert "poll_options" not in body
    assert "attachment_uids" not in body


def test_formdata_list_field_empty_string_dropped():
    """A single empty string (browser hidden input) is treated as absent."""
    fd = FormData([("attachment_uids", ""), ("poll_options", "")])
    body = _formdata_to_dict(fd, PostForm)
    assert "attachment_uids" not in body
    assert "poll_options" not in body


def test_formdata_list_field_mixed_empty_and_real():
    """Real values survive even when some are empty."""
    fd = FormData([
        ("attachment_uids", ""),
        ("attachment_uids", "uid-real"),
    ])
    body = _formdata_to_dict(fd, PostForm)
    assert body["attachment_uids"] == ["uid-real"]


def test_formdata_literal_fields_are_scalar():
    """Literal-typed fields (e.g. target_type in CommentForm) are treated as
    scalars - the origin check must not crash on typing.Literal."""
    fd = FormData([
        ("content", "Great comment here!"),
        ("target_type", "post"),
        ("post_uid", "abc123"),
    ])
    body = _formdata_to_dict(fd, CommentForm)
    assert body["target_type"] == "post"
    assert body["post_uid"] == "abc123"


def test_formdata_literal_form_from_browser():
    """CommentForm from a real browser sending empty hidden inputs."""
    fd = FormData([
        ("content", "Nice post! Great stuff."),
        ("target_type", "post"),
        ("target_uid", ""),
        ("post_uid", "abc123"),
        ("parent_uid", ""),
        ("attachment_uids", ""),
    ])
    body = _formdata_to_dict(fd, CommentForm)
    assert body["content"] == "Nice post! Great stuff."
    assert body["target_type"] == "post"
    assert body["target_uid"] == ""
    assert body["post_uid"] == "abc123"
    assert body["parent_uid"] == ""
    assert "attachment_uids" not in body  # empty → dropped


def test_formdata_vote_form():
    """value: int field with form data - string '1' is kept for Pydantic coerce."""
    fd = FormData([("value", "1")])
    body = _formdata_to_dict(fd, VoteForm)
    assert body["value"] == "1"  # Pydantic coerces str→int


def test_formdata_issue_status_literal():
    """IssueStatusForm has Literal['open','closed'] - must not crash."""
    fd = FormData([("status", "open")])
    body = _formdata_to_dict(fd, IssueStatusForm)
    assert body["status"] == "open"


def test_formdata_empty_form():
    """An empty FormData produces an empty dict."""
    fd = FormData()
    body = _formdata_to_dict(fd, PostForm)
    assert body == {}


def test_formdata_unknown_keys_ignored():
    """Keys not in the model are simply not included."""
    fd = FormData([("nosuchfield", "val"), ("content", "x" * 20)])
    body = _formdata_to_dict(fd, PostForm)
    assert "nosuchfield" not in body
    assert body["content"] == "x" * 20


# ── model_validate integration tests (without running server) ──────────────

def test_postform_validate_minimal_form():
    """A minimal form submission validates successfully."""
    fd = FormData([("content", "Hello world post"), ("title", "Hi")])
    body = _formdata_to_dict(fd, PostForm)
    inst = PostForm.model_validate(body)
    assert inst.content == "Hello world post"
    assert inst.title == "Hi"
    assert inst.topic == "random"  # default
    assert inst.attachment_uids == []  # default
    assert inst.poll_options == []  # default


def test_postform_validate_full_browser_form():
    """Browser form with all fields (including empty hidden inputs)."""
    fd = FormData([
        ("content", "A full browser form post"),
        ("title", "Full Form"),
        ("topic", "random"),
        ("project_uid", ""),
        ("attachment_uids", ""),
        ("poll_question", ""),
        ("poll_options", ""),
    ])
    body = _formdata_to_dict(fd, PostForm)
    inst = PostForm.model_validate(body)
    assert inst.content == "A full browser form post"
    assert inst.title == "Full Form"
    assert inst.project_uid == ""
    assert inst.poll_question == ""
    assert inst.attachment_uids == []
    assert inst.poll_options == []


def test_postform_validate_json_body():
    """A JSON-style dict (no FormData) validates correctly."""
    body = {
        "content": "JSON body post",
        "title": "JSON Post",
        "topic": "random",
        "attachment_uids": ["uid1", "uid2"],
        "poll_question": "Question?",
        "poll_options": ["A", "B"],
    }
    inst = PostForm.model_validate(body)
    assert inst.content == "JSON body post"
    assert inst.attachment_uids == ["uid1", "uid2"]
    assert inst.poll_options == ["A", "B"]


def test_postform_validate_multi_value_poll():
    """Multi-value form poll options become a list."""
    fd = FormData([
        ("content", "Poll post here"),
        ("poll_question", "Q?"),
        ("poll_options", "Opt 1"),
        ("poll_options", "Opt 2"),
        ("poll_options", "Opt 3"),
    ])
    body = _formdata_to_dict(fd, PostForm)
    inst = PostForm.model_validate(body)
    assert inst.poll_options == ["Opt 1", "Opt 2", "Opt 3"]
    assert inst.content == "Poll post here"


def test_commentform_validate_browser():
    """CommentForm validates with browser-style form data."""
    fd = FormData([
        ("content", "Nice post! Great stuff here."),
        ("target_type", "post"),
        ("target_uid", ""),
        ("post_uid", "abc123"),
        ("parent_uid", ""),
        ("attachment_uids", ""),
    ])
    body = _formdata_to_dict(fd, CommentForm)
    inst = CommentForm.model_validate(body)
    assert inst.content == "Nice post! Great stuff here."
    assert inst.target_type == "post"
    assert inst.post_uid == "abc123"
    assert inst.attachment_uids == []


def test_commentform_validate_json():
    """CommentForm validates JSON dict."""
    body = {
        "content": "JSON comment",
        "target_type": "post",
        "post_uid": "abc123",
        "attachment_uids": ["a1"],
    }
    inst = CommentForm.model_validate(body)
    assert inst.content == "JSON comment"
    assert inst.attachment_uids == ["a1"]


def test_loginform_validate_json():
    """LoginForm with JSON body."""
    body = {
        "email": "user@test.com",
        "password": "secret123",
        "remember_me": "",
        "next": "",
    }
    inst = LoginForm.model_validate(body)
    assert inst.email == "user@test.com"


def test_signup_form_rejected_short_password():
    """SignupForm raises ValidationError for short passwords."""
    from pydantic import ValidationError
    import pytest

    fd = FormData([
        ("username", "valid_user"),
        ("email", "u@t.com"),
        ("password", "short"),
        ("confirm_password", "short"),
    ])
    body = _formdata_to_dict(fd, SignupForm)
    with pytest.raises(ValidationError):
        SignupForm.model_validate(body)


def test_postform_rejected_content_too_short():
    """PostForm requires content >= 10 chars."""
    from pydantic import ValidationError
    import pytest

    fd = FormData([("content", "short")])
    body = _formdata_to_dict(fd, PostForm)
    with pytest.raises(ValidationError):
        PostForm.model_validate(body)


# ── json_or_form dependency factory ─────────────────────────────────────────

def test_json_or_form_returns_callable():
    dep = json_or_form(PostForm)
    assert isinstance(dep, _JsonOrForm)
    assert dep.model is PostForm
