"""Tests for ``arautopilot.core.user``."""

from __future__ import annotations

import pytest
from pydantic import ValidationError

from arautopilot.core.rbac import Role
from arautopilot.core.user import User


def test_create_user_with_valid_pin() -> None:
    u = User.create(display_name="Test Engineer", role=Role.ENGINEER, pin="1234")
    assert u.display_name == "Test Engineer"
    assert u.role is Role.ENGINEER
    assert u.pin_hash.startswith("pbkdf2_sha256$")
    assert u.active is True
    assert u.last_login_at is None


def test_verify_correct_pin() -> None:
    u = User.create(display_name="X", role=Role.USER, pin="9876")
    assert u.verify_pin("9876") is True


def test_verify_incorrect_pin() -> None:
    u = User.create(display_name="X", role=Role.USER, pin="9876")
    assert u.verify_pin("0000") is False
    assert u.verify_pin("987") is False  # too short, treated as invalid
    assert u.verify_pin("98765") is False
    assert u.verify_pin("") is False


def test_pin_hash_is_different_each_time_for_same_pin() -> None:
    """Salting must make the hashes diverge even for identical PINs."""
    a = User.create(display_name="A", role=Role.USER, pin="1234")
    b = User.create(display_name="B", role=Role.USER, pin="1234")
    assert a.pin_hash != b.pin_hash
    # But both verify against the same PIN.
    assert a.verify_pin("1234")
    assert b.verify_pin("1234")


def test_set_pin_returns_new_instance_with_new_hash() -> None:
    u = User.create(display_name="A", role=Role.USER, pin="1234")
    old_hash = u.pin_hash
    u2 = u.set_pin("5678")
    assert u2.pin_hash != old_hash
    assert u2.verify_pin("5678") is True
    assert u2.verify_pin("1234") is False


def test_pin_must_be_numeric_4_to_8_digits() -> None:
    with pytest.raises(ValueError):
        User.create(display_name="A", role=Role.USER, pin="abc")
    with pytest.raises(ValueError):
        User.create(display_name="A", role=Role.USER, pin="12")
    with pytest.raises(ValueError):
        User.create(display_name="A", role=Role.USER, pin="123456789")
    with pytest.raises(ValueError):
        User.create(display_name="A", role=Role.USER, pin="12 34")


def test_pin_hash_field_validator_rejects_garbage() -> None:
    with pytest.raises(ValidationError):
        User(display_name="A", role=Role.USER, pin_hash="not-a-real-hash")
    with pytest.raises(ValidationError):
        # Right shape but wrong scheme.
        User(display_name="A", role=Role.USER,
             pin_hash="md5$1000$xxxxxxxxxx$yyyyyyyyyy")


def test_user_rejects_unknown_field() -> None:
    # Direct construction with unknown field should fail (extra="forbid").
    with pytest.raises(ValidationError):
        User(display_name="A", role=Role.USER, pin_hash="pbkdf2_sha256$200000$xx$yy",
             unknown="x")  # type: ignore[call-arg]


def test_touch_login_advances_timestamp() -> None:
    u = User.create(display_name="A", role=Role.USER, pin="1234")
    assert u.last_login_at is None
    u2 = u.touch_login()
    assert u2.last_login_at is not None
    assert u2.user_id == u.user_id


def test_vessel_scoped_user() -> None:
    u = User.create(display_name="Captain", role=Role.OWNER, pin="4242",
                    vessel_id="abc123")
    assert u.vessel_id == "abc123"


def test_serialisation_round_trip() -> None:
    import json
    u = User.create(display_name="Test", role=Role.ENGINEER, pin="2468")
    data = u.model_dump(mode="json")
    text = json.dumps(data)
    rebuilt = User.model_validate(json.loads(text))
    assert rebuilt.display_name == u.display_name
    assert rebuilt.role == u.role
    assert rebuilt.pin_hash == u.pin_hash
    assert rebuilt.verify_pin("2468") is True
