"""
Kontosammenslåing (ADR-080) -- dekker konfliktkartet fra planen: org-rolle-
kollisjon, player-sammenslåing med ekte team_roster-kollisjon, venn-
selvlenke, delt-venn-dedup, sekundær-e-post-overføring, token-utløp, og en
full ende-til-ende-sammenslåing som beviser taper-kontoen faktisk er borte.
"""

import pytest
from fastapi import HTTPException

from app.account_merge import compute_merge_preview, execute_merge
from app.auth import resolve_user_id_by_email

from tests.conftest import (
    add_membership,
    create_friendship,
    create_org,
    create_player,
    create_team,
    create_team_roster,
    create_tournament,
    create_user,
)

import app.db as app_db


async def test_resolve_user_id_by_email_checks_primary_then_secondary(pool):
    user_id = await create_user(email="primary@pytest.invalid")
    async with app_db.plain_connection() as conn:
        resolved = await resolve_user_id_by_email(conn, "primary@pytest.invalid")
        assert resolved == user_id

        await conn.execute(
            "INSERT INTO user_secondary_email (user_id, email) VALUES ($1, $2)", user_id, "secondary@pytest.invalid"
        )
        resolved = await resolve_user_id_by_email(conn, "secondary@pytest.invalid")
        assert resolved == user_id

        assert await resolve_user_id_by_email(conn, "nobody@pytest.invalid") is None


async def test_preview_shows_org_membership_conflict_and_resulting_role(pool):
    org_id = await create_org()
    keeper_id = await create_user()
    loser_id = await create_user()
    await add_membership(org_id, keeper_id, role="member")
    await add_membership(org_id, loser_id, role="owner")

    preview = await compute_merge_preview(keeper_id, loser_id)
    assert len(preview.organization_memberships) == 1
    m = preview.organization_memberships[0]
    assert m.keeper_role == "member"
    assert m.loser_role == "owner"
    assert m.resulting_role == "owner"


async def test_merge_resolves_org_membership_to_highest_role(pool):
    org_id = await create_org()
    keeper_id = await create_user()
    loser_id = await create_user()
    await add_membership(org_id, keeper_id, role="member")
    await add_membership(org_id, loser_id, role="owner")

    await execute_merge(keeper_id, loser_id)

    async with app_db.plain_connection() as conn:
        rows = await conn.fetch("SELECT user_id::text AS user_id, role FROM organization_membership WHERE organization_id = $1", org_id)
    assert len(rows) == 1
    assert rows[0]["user_id"] == keeper_id
    assert rows[0]["role"] == "owner"


async def test_merge_repoints_membership_when_only_loser_is_member(pool):
    org_id = await create_org()
    keeper_id = await create_user()
    loser_id = await create_user()
    await add_membership(org_id, loser_id, role="admin")

    await execute_merge(keeper_id, loser_id)

    async with app_db.plain_connection() as conn:
        rows = await conn.fetch("SELECT user_id::text AS user_id, role FROM organization_membership WHERE organization_id = $1", org_id)
    assert len(rows) == 1
    assert rows[0]["user_id"] == keeper_id
    assert rows[0]["role"] == "admin"


async def test_merge_merges_player_rows_and_dedupes_team_roster_collision(pool):
    """Begge kontoer har hver sin player-rad i SAMME org, begge rostret på
    SAMME lag -- ekte UNIQUE(team_id, player_id)-kollisjon. Etter
    sammenslåing skal det finnes NØYAKTIG ÉN player-rad og ÉN team_roster-
    rad, eid av keeper."""
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    keeper_id = await create_user()
    loser_id = await create_user()

    keeper_player_id = await create_player(org_id, user_id=keeper_id, display_name="Keeper")
    loser_player_id = await create_player(org_id, user_id=loser_id, display_name="Loser")

    tournament_id = await create_tournament(org_id)
    team_id = await create_team(org_id, tournament_id)
    await create_team_roster(org_id, team_id, keeper_player_id)
    await create_team_roster(org_id, team_id, loser_player_id)

    preview = await compute_merge_preview(keeper_id, loser_id)
    org_preview = next(p for p in preview.player_links if p.organization_id == org_id)
    assert org_preview.keeper_has_player is True
    assert org_preview.loser_has_player is True
    assert org_preview.colliding_registrations == 1

    await execute_merge(keeper_id, loser_id)

    async with app_db.org_connection(org_id) as conn:
        player_rows = await conn.fetch("SELECT id::text AS id, user_id::text AS user_id FROM player WHERE organization_id = $1", org_id)
        roster_rows = await conn.fetch("SELECT player_id::text AS player_id FROM team_roster WHERE team_id = $1", team_id)
    assert len(player_rows) == 1
    assert player_rows[0]["user_id"] == keeper_id
    assert len(roster_rows) == 1
    assert roster_rows[0]["player_id"] == player_rows[0]["id"]


async def test_merge_repoints_player_when_only_loser_has_one(pool):
    org_id = await create_org()
    keeper_id = await create_user()
    loser_id = await create_user()
    await create_player(org_id, user_id=loser_id, display_name="Loser")

    await execute_merge(keeper_id, loser_id)

    async with app_db.org_connection(org_id) as conn:
        row = await conn.fetchrow("SELECT user_id::text AS user_id FROM player WHERE organization_id = $1", org_id)
    assert row["user_id"] == keeper_id


async def test_merge_deletes_self_loop_friendship(pool):
    """Keeper og taper var venner med HVERANDRE -- må slettes, ikke
    re-pekes (ville brutt CHECK(requester <> addressee))."""
    keeper_id = await create_user()
    loser_id = await create_user()
    await create_friendship(keeper_id, loser_id, status="accepted")

    await execute_merge(keeper_id, loser_id)

    async with app_db.plain_connection() as conn:
        count = await conn.fetchval(
            "SELECT count(*) FROM friendship WHERE requester_user_id = $1 OR addressee_user_id = $1", keeper_id
        )
    assert count == 0


async def test_merge_dedupes_shared_friend_prefers_accepted(pool):
    """Keeper og taper er BEGGE venn med samme tredjeperson -- keeperens er
    'pending', taperens er 'accepted'. Etter sammenslåing skal keeper ha
    NØYAKTIG ÉN vennskapsrad med tredjepersonen, status 'accepted'."""
    keeper_id = await create_user()
    loser_id = await create_user()
    third_id = await create_user()
    await create_friendship(keeper_id, third_id, status="pending")
    await create_friendship(loser_id, third_id, status="accepted")

    await execute_merge(keeper_id, loser_id)

    async with app_db.plain_connection() as conn:
        rows = await conn.fetch(
            "SELECT status FROM friendship WHERE (requester_user_id = $1 AND addressee_user_id = $2) "
            "OR (requester_user_id = $2 AND addressee_user_id = $1)",
            keeper_id,
            third_id,
        )
    assert len(rows) == 1
    assert rows[0]["status"] == "accepted"


async def test_merge_transfers_secondary_email_and_demotes_loser_primary(pool):
    keeper_id = await create_user(email="keeper@pytest.invalid")
    loser_id = await create_user(email="loser@pytest.invalid")

    await execute_merge(keeper_id, loser_id)

    async with app_db.plain_connection() as conn:
        row = await conn.fetchrow(
            "SELECT user_id::text AS user_id FROM user_secondary_email WHERE email = 'loser@pytest.invalid'"
        )
    assert row is not None
    assert row["user_id"] == keeper_id


async def test_full_end_to_end_merge_deletes_loser_account_and_frees_email(pool):
    """Keeper medlem i org A (member), taper medlem i BÅDE org A (owner --
    kollisjon, owner skal vinne) og org B (admin -- ren repeking). Pluss
    venner. Bekreft alt riktig, OG at taper-kontoen faktisk er borte
    (samme bevis-mønster som ADR-032 sin egen e-postbytte-test: ny
    innlogging med taperens gamle e-post oppretter en FRISK konto)."""
    org_a = await create_org()
    org_b = await create_org()
    keeper_id = await create_user(email="keeper2@pytest.invalid")
    loser_id = await create_user(email="loser2@pytest.invalid")
    friend_id = await create_user()

    await add_membership(org_a, keeper_id, role="member")
    await add_membership(org_a, loser_id, role="owner")
    await add_membership(org_b, loser_id, role="admin")
    await create_friendship(keeper_id, friend_id, status="accepted")

    result = await execute_merge(keeper_id, loser_id)
    assert result.keeper_id == keeper_id
    assert result.merged_organization_count == 2

    async with app_db.plain_connection() as conn:
        assert await conn.fetchval("SELECT role FROM organization_membership WHERE organization_id = $1 AND user_id = $2", org_a, keeper_id) == "owner"
        assert await conn.fetchval("SELECT role FROM organization_membership WHERE organization_id = $1 AND user_id = $2", org_b, keeper_id) == "admin"
        assert await conn.fetchval("SELECT EXISTS(SELECT 1 FROM app_user WHERE id = $1)", loser_id) is False

        # Taperens gamle e-post er nå FRI (kun keeperens sekundær-e-post,
        # ikke lenger noen konto sin PRIMÆRE) -- en ny innlogging med den
        # skal derfor opprette en FRISK, tom konto, ikke gjenopplive taperen.
        freed_email_owner = await conn.fetchval("SELECT id FROM app_user WHERE email = 'loser2@pytest.invalid'")
        assert freed_email_owner is None
        secondary_owner = await conn.fetchval(
            "SELECT user_id::text AS user_id FROM user_secondary_email WHERE email = 'loser2@pytest.invalid'"
        )
        assert secondary_owner == keeper_id


async def test_confirm_rejects_expired_token(pool):
    from app.routers.account_merge import AccountMergeConfirm, confirm_account_merge
    from app.routers.auth import _hash_secret

    keeper_id = await create_user()
    loser_id = await create_user()
    raw_token = "expired-test-token"

    async with app_db.plain_connection() as conn:
        await conn.execute(
            "INSERT INTO account_merge_token (initiator_user_id, target_user_id, token_hash, expires_at) "
            "VALUES ($1, $2, $3, now() - interval '1 hour')",
            keeper_id,
            loser_id,
            _hash_secret(raw_token),
        )

    with pytest.raises(HTTPException) as exc_info:
        await confirm_account_merge(AccountMergeConfirm(token=raw_token))
    assert exc_info.value.status_code == 401
    # Kontoene består uendret -- avvist token skal aldri utløse sammenslåing.
    async with app_db.plain_connection() as conn:
        assert await conn.fetchval("SELECT EXISTS(SELECT 1 FROM app_user WHERE id = $1)", loser_id) is True


async def test_confirm_rejects_already_consumed_token(pool):
    from app.routers.account_merge import AccountMergeConfirm, confirm_account_merge
    from app.routers.auth import _hash_secret

    keeper_id = await create_user()
    loser_id = await create_user()
    raw_token = "already-used-test-token"

    async with app_db.plain_connection() as conn:
        await conn.execute(
            "INSERT INTO account_merge_token (initiator_user_id, target_user_id, token_hash, expires_at, consumed_at) "
            "VALUES ($1, $2, $3, now() + interval '1 hour', now())",
            keeper_id,
            loser_id,
            _hash_secret(raw_token),
        )

    with pytest.raises(HTTPException) as exc_info:
        await confirm_account_merge(AccountMergeConfirm(token=raw_token))
    assert exc_info.value.status_code == 401
