import pytest

from pygitweb.permissions import (
	PERMISSION_ADD_PROJECTS,
	PERMISSION_SETTINGS,
	Permission,
	PermissionsMap,
	has_ref_write_permission,
	parse_permission_key,
	permission_key,
	project_has_write_ref_rules,
	project_matches_scope,
	ref_permission_subject,
	write_ref_project_segment,
)


def test_parse_global_keys() -> None:
	assert parse_permission_key(PERMISSION_ADD_PROJECTS) is not None
	assert parse_permission_key(PERMISSION_SETTINGS) is not None
	assert parse_permission_key("pgw.unknown") is None
	assert parse_permission_key("*") is None


def test_parse_scoped_key() -> None:
	parsed = parse_permission_key("pgw.tasks.my/repo")
	assert parsed is not None
	assert parsed.permission is Permission.TASKS
	assert parsed.project_scope == "my/repo"


def test_permission_key_roundtrip() -> None:
	assert permission_key(Permission.READ, "foo") == "pgw.read.foo"
	assert permission_key(Permission.WRITE, "foo") == "pgw.write.foo"
	assert permission_key(Permission.BOARDS, "foo") == "pgw.boards.foo"
	assert permission_key(Permission.MR_CREATE, "foo") == "pgw.mr.create.foo"
	assert permission_key(Permission.MR_MERGE, "foo") == "pgw.mr.merge.foo"
	assert permission_key(Permission.HOOKS, "foo") == "pgw.hooks.foo"


def test_parse_read_write_scoped_keys() -> None:
	read = parse_permission_key("pgw.read.team/*")
	assert read is not None
	assert read.permission is Permission.READ
	assert read.project_scope == "team/*"
	write = parse_permission_key("pgw.write.private")
	assert write is not None
	assert write.permission is Permission.WRITE
	assert write.project_scope == "private"


def test_parse_mr_scoped_keys() -> None:
	create = parse_permission_key("pgw.mr.create.demo")
	assert create is not None
	assert create.permission is Permission.MR_CREATE
	merge = parse_permission_key("pgw.mr.merge.demo")
	assert merge is not None
	assert merge.permission is Permission.MR_MERGE


def test_parse_write_ref_scoped_keys() -> None:
	ref = parse_permission_key("pgw.write_ref.demo/tags/v*")
	assert ref is not None
	assert ref.permission is Permission.WRITE_REF
	assert ref.project_scope == "demo/tags/v*"
	heads = parse_permission_key("pgw.write_ref.dev/heads/alice.*")
	assert heads is not None
	assert heads.permission is Permission.WRITE_REF
	assert heads.project_scope == "dev/heads/alice.*"
	assert parse_permission_key("pgw.write_branch.dev/alice.*") is None
	assert parse_permission_key("pgw.read_branch.team/app-main") is None


def test_project_glob() -> None:
	assert project_matches_scope("team/app", "team/*")
	assert not project_matches_scope("other/app", "team/*")
	assert project_matches_scope("anything", "*")


def test_ref_permission_subject() -> None:
	assert ref_permission_subject("demo", "refs/heads/main") == "demo/heads/main"
	assert ref_permission_subject("demo", "refs/tags/v1.0.0") == "demo/tags/v1.0.0"
	assert ref_permission_subject("demo", "refs/notes/commits") == "demo/notes/commits"
	assert ref_permission_subject("demo", "HEAD") is None
	assert write_ref_project_segment("demo/heads/main") == "demo"
	assert write_ref_project_segment("*/tags/v*") == "*"
	assert write_ref_project_segment("solo") == "solo"


def test_superuser_grants_all() -> None:
	m = PermissionsMap.model_validate({
		"*": ["admin@example.com"],
		"pgw.tasks.secret": ["other@example.com"],
	})
	assert m.has_permission("admin@example.com", Permission.TASKS, "secret")
	assert m.has_permission("admin@example.com", Permission.SETTINGS)
	assert m.has_permission("other@example.com", Permission.TASKS, "secret")
	assert not m.has_permission("other@example.com", Permission.SETTINGS)
	assert not m.has_permission("other@example.com", Permission.TASKS, "public")


def test_scoped_grant() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.comments.proj-a": ["reader@example.com"],
		"pgw.boards.team/*": ["lead@example.com"],
	})
	assert m.has_permission("reader@example.com", Permission.COMMENTS, "proj-a")
	assert not m.has_permission("reader@example.com", Permission.TASKS, "proj-a")
	assert m.has_permission("lead@example.com", Permission.BOARDS, "team/widget")
	assert not m.has_permission("lead@example.com", Permission.BOARDS, "other/widget")


def test_email_case_insensitive() -> None:
	m = PermissionsMap.model_validate({"*": [], PERMISSION_SETTINGS: ["Admin@Example.COM"]})
	assert m.has_permission("admin@example.com", Permission.SETTINGS)


def test_rejects_unknown_key() -> None:
	with pytest.raises(ValueError, match="unknown permission key"):
		PermissionsMap.model_validate({"pgw.notreal": ["a@b.com"]})
	with pytest.raises(ValueError, match="unknown permission key"):
		PermissionsMap.model_validate({"pgw.write_branch.demo/main": ["a@b.com"]})


def test_ensures_wildcard_key() -> None:
	m = PermissionsMap.model_validate({PERMISSION_ADD_PROJECTS: ["u@x.com"]})
	assert "*" in m.root


def test_default_access_grants_read_write_to_any_authenticated_user() -> None:
	m = PermissionsMap.default_access()
	assert m.has_permission("alice", Permission.READ, "secret")
	assert m.has_permission("bob@example.com", Permission.WRITE, "team/app")
	assert not m.has_permission("", Permission.READ, "secret")
	assert not m.has_permission(None, Permission.WRITE, "secret")


def test_scoped_read_write_without_wildcard_principal() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.read.public": ["reader@example.com"],
		"pgw.write.public": ["writer@example.com"],
	})
	assert m.has_permission("reader@example.com", Permission.READ, "public")
	assert not m.has_permission("reader@example.com", Permission.WRITE, "public")
	assert m.has_permission("writer@example.com", Permission.WRITE, "public")
	assert not m.has_permission("writer@example.com", Permission.READ, "private")


def test_owner_grants_read_and_write() -> None:
	from unittest.mock import patch

	from pygitweb.auth import can_read_project, can_write_project
	from pygitweb.config import settings
	from pygitweb.sessions import create_session

	with (
		patch.object(settings, "AUTH", True),
		patch("pygitweb.auth.git_get_project_owner", return_value="owner@example.com"),
		patch(
			"pygitweb.auth.auth_config.oauth_permissions",
			PermissionsMap.model_validate({"*": []}),
		),
	):
		token = create_session("someone", email="owner@example.com", auth_method="local")
		assert can_read_project("secret", token)
		assert can_write_project("secret", token)
		assert not can_read_project("secret", None)


def test_has_ref_write_permission_falls_back_to_project_scope() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.write.demo": ["writer@example.com"],
	})
	assert not project_has_write_ref_rules(m, "demo")
	assert has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/main")
	assert has_ref_write_permission(m, "writer@example.com", "demo", "refs/tags/v1")
	assert has_ref_write_permission(m, "writer@example.com", "demo", "refs/notes/commits")
	assert not has_ref_write_permission(m, "other@example.com", "demo", "refs/heads/main")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "HEAD")


def test_write_ref_rules_are_per_project() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.write.demo": ["writer@example.com"],
		"pgw.write.other": ["writer@example.com"],
		"pgw.write_ref.other/heads/main": ["ref-writer@example.com"],
	})
	assert not project_has_write_ref_rules(m, "demo")
	assert project_has_write_ref_rules(m, "other")
	assert has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/main")
	assert not has_ref_write_permission(m, "writer@example.com", "other", "refs/heads/main")
	assert has_ref_write_permission(m, "ref-writer@example.com", "other", "refs/heads/main")


def test_has_ref_write_permission_whitelist_when_write_ref_defined() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.write.demo": ["writer@example.com"],
		"pgw.write_ref.demo/tags/v*": ["release@example.com"],
		"pgw.write_ref.demo/notes/*": ["alice@example.com"],
		"pgw.write_ref.demo/heads/main": ["ref-writer@example.com"],
		"pgw.write_ref.demo/heads/feature*": ["writer@example.com"],
	})
	assert project_has_write_ref_rules(m, "demo")
	assert has_ref_write_permission(m, "release@example.com", "demo", "refs/tags/v1.2")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/tags/v1.2")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/tags/nightly")
	assert has_ref_write_permission(m, "alice@example.com", "demo", "refs/notes/commits")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/notes/commits")
	assert has_ref_write_permission(m, "ref-writer@example.com", "demo", "refs/heads/main")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/main")
	assert has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/feature/x")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/develop")


def test_write_ref_whitelist_empty_grant_list_still_enables_mode() -> None:
	m = PermissionsMap.model_validate({
		"*": [],
		"pgw.write.demo": ["writer@example.com"],
		"pgw.write_ref.demo/heads/main": [],
	})
	assert project_has_write_ref_rules(m, "demo")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/heads/main")
	assert not has_ref_write_permission(m, "writer@example.com", "demo", "refs/tags/v1")


def test_write_ref_superuser_bypasses_whitelist() -> None:
	m = PermissionsMap.model_validate({
		"*": ["admin@example.com"],
		"pgw.write.demo": ["writer@example.com"],
		"pgw.write_ref.demo/heads/main": ["ref-writer@example.com"],
	})
	assert has_ref_write_permission(m, "admin@example.com", "demo", "refs/heads/main")
	assert has_ref_write_permission(m, "admin@example.com", "demo", "refs/tags/v1")