"""Phase 6.3 — Enterprise Sales Collaboration Platform tests."""
from __future__ import annotations

from datetime import datetime, timedelta, timezone
from uuid import uuid4

import pytest
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from app.core.database import engine
from app.events.publisher import get_event_publisher
from app.events.types import CrmEventType
from app.models.collaboration import Comment, SalesTimelineEvent
from app.models.types import (
    CallDirection,
    CollaborationEntityType,
    TimelineEventType,
)
from app.schemas.collaboration import (
    BookmarkCreate,
    CallLogCreate,
    ChecklistItemCreate,
    CommentCreate,
    MeetingCreate,
    MeetingFinishRequest,
    SalesActivityCreate,
    TaskCreate,
    TeamMemberCreate,
    TeamRoleCreate,
)
from app.services.collaboration_services import (
    BookmarkService,
    CallService,
    CommentService,
    MeetingService,
    SalesActivityService,
    SalesTeamService,
    TaskService,
    TimelineService,
)
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
from app.validators.collaboration import (
    ensure_call_has_opportunity_link,
    ensure_meeting_window,
    validate_comment_body,
)
from shared.exceptions import AppError
from shared.security import CurrentUser


def _session_factory():
    return async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)


@pytest.mark.asyncio
async def test_activity_timeline_and_priority(db_setup):
    Session = _session_factory()
    async with Session() as session:
        act = await SalesActivityService(session).create(
            TENANT_A,
            SalesActivityCreate(
                subject="Follow up demo",
                activity_type="demo",
                priority="high",
                opportunity_id=uuid4(),
            ),
        )
        assert act.priority.value == "high"
        assert act.activity_type.value == "demo"
        events = get_event_publisher().published
        assert any(e.event_type == CrmEventType.ACTIVITY_CREATED.value for e in events)
        assert any(e.event_type == CrmEventType.TIMELINE_UPDATED.value for e in events)
        timeline = await TimelineService(session).list(TENANT_A, offset=0, limit=20)
        assert any(t.event_type == TimelineEventType.ACTIVITY_CREATED for t in timeline)


@pytest.mark.asyncio
async def test_task_checklist_and_complete(db_setup):
    Session = _session_factory()
    async with Session() as session:
        svc = TaskService(session)
        task = await svc.create(
            TENANT_A,
            TaskCreate(title="Prepare proposal", assignee_user_id="u1"),
        )
        item = await svc.add_checklist_item(
            TENANT_A,
            ChecklistItemCreate(task_id=task.id, title="Draft slides"),
        )
        await svc.complete_checklist_item(TENANT_A, item.id)
        done = await svc.complete(TENANT_A, task.id)
        assert done.status == "completed"
        assert done.completion_percent == 100
        assert any(
            e.event_type == CrmEventType.TASK_COMPLETED.value
            for e in get_event_publisher().published
        )


@pytest.mark.asyncio
async def test_meeting_call_comment_bookmark(db_setup):
    Session = _session_factory()
    async with Session() as session:
        start = datetime.now(timezone.utc)
        meeting = await MeetingService(session).create(
            TENANT_A,
            MeetingCreate(
                subject="Discovery",
                start_at=start,
                end_at=start + timedelta(hours=1),
                opportunity_id=uuid4(),
                location="HQ",
            ),
        )
        finished = await MeetingService(session).finish(
            TENANT_A,
            meeting.id,
            MeetingFinishRequest(outcome="qualified"),
        )
        assert finished.is_finished is True

        call = await CallService(session).log(
            TENANT_A,
            CallLogCreate(
                direction=CallDirection.OUTBOUND,
                opportunity_id=uuid4(),
                duration_seconds=120,
                notes="Left voicemail",
            ),
        )
        assert call.duration_seconds == 120

        actor = CurrentUser(user_id="u1", username="u1", roles=["tenant_admin"])
        comment = await CommentService(session).create(
            TENANT_A,
            CommentCreate(
                entity_type=CollaborationEntityType.OPPORTUNITY,
                entity_id=uuid4(),
                body="Need pricing review",
                mention_user_ids=["u2"],
                is_pinned=True,
            ),
            actor=actor,
        )
        assert comment.version == 1
        assert comment.is_pinned is True

        bookmark = await BookmarkService(session).add_bookmark(
            TENANT_A,
            "u1",
            BookmarkCreate(
                entity_type=CollaborationEntityType.LEAD,
                entity_id=uuid4(),
                label="Hot lead",
            ),
        )
        assert bookmark.user_id == "u1"

        team = SalesTeamService(session)
        role = await team.create_role(
            TENANT_A,
            TeamRoleCreate(code="mgr", name="Manager", role_kind="manager"),
        )
        member = await team.add_member(
            TENANT_A,
            TeamMemberCreate(
                user_id="u1",
                team_key="enterprise",
                role_id=role.id,
                role_code="manager",
            ),
        )
        assert member.team_key == "enterprise"


@pytest.mark.asyncio
async def test_timeline_immutable(db_setup):
    Session = _session_factory()
    async with Session() as session:
        event = await TimelineService(session).append(
            tenant_id=TENANT_A,
            event_type=TimelineEventType.NOTE_ADDED,
            entity_type=CollaborationEntityType.LEAD,
            entity_id=uuid4(),
            title="Note",
            commit=True,
        )
        with pytest.raises(AppError) as exc:
            await TimelineService(session).reject_update(TENANT_A, event.id)
        assert exc.value.error_code == "timeline_immutable"


@pytest.mark.asyncio
async def test_collaboration_api_flow(client):
    opp_id = str(uuid4())
    activity = await client.post(
        "/api/v1/activities",
        json={
            "subject": "Negotiation call",
            "activity_type": "negotiation",
            "priority": "urgent",
            "opportunity_id": opp_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert activity.status_code == 201, activity.text

    task = await client.post(
        "/api/v1/tasks",
        json={"title": "Send quote", "opportunity_id": opp_id},
        headers=tenant_headers(TENANT_A),
    )
    assert task.status_code == 201, task.text
    completed = await client.post(
        f"/api/v1/tasks/{task.json()['id']}/complete",
        headers=tenant_headers(TENANT_A),
    )
    assert completed.status_code == 200

    start = datetime.now(timezone.utc)
    meeting = await client.post(
        "/api/v1/meetings",
        json={
            "subject": "Kickoff",
            "start_at": start.isoformat(),
            "end_at": (start + timedelta(minutes=30)).isoformat(),
            "opportunity_id": opp_id,
            "online_meeting_url": "https://meet.example/abc",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert meeting.status_code == 201, meeting.text
    finished = await client.post(
        f"/api/v1/meetings/{meeting.json()['id']}/finish",
        json={"outcome": "done"},
        headers=tenant_headers(TENANT_A),
    )
    assert finished.status_code == 200

    call = await client.post(
        "/api/v1/calls",
        json={
            "direction": "inbound",
            "opportunity_id": opp_id,
            "duration_seconds": 45,
            "recording_ref": "rec-1",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert call.status_code == 201, call.text

    comment = await client.post(
        "/api/v1/comments",
        json={
            "entity_type": "opportunity",
            "entity_id": opp_id,
            "body": "Looks good @manager",
            "mention_user_ids": ["manager-1"],
        },
        headers=tenant_headers(TENANT_A),
    )
    assert comment.status_code == 201, comment.text

    bookmark = await client.post(
        "/api/v1/bookmarks",
        json={"entity_type": "opportunity", "entity_id": opp_id},
        headers=tenant_headers(TENANT_A),
    )
    assert bookmark.status_code == 201

    timeline = await client.get(
        f"/api/v1/timeline/opportunities/{opp_id}",
        headers=tenant_headers(TENANT_A),
    )
    assert timeline.status_code == 200
    assert len(timeline.json()) >= 1

    other = await client.get("/api/v1/tasks", headers=tenant_headers(TENANT_B))
    assert other.status_code == 200
    assert other.json() == []


def test_collaboration_validators():
    with pytest.raises(AppError):
        ensure_meeting_window(
            datetime(2026, 1, 1, tzinfo=timezone.utc),
            datetime(2026, 1, 1, tzinfo=timezone.utc),
        )
    with pytest.raises(AppError):
        ensure_call_has_opportunity_link(None)
    assert validate_comment_body(" hi ") == "hi"
    with pytest.raises(AppError):
        validate_comment_body("  ")


def test_collaboration_models_tenant_ready():
    assert hasattr(Comment, "tenant_id")
    assert hasattr(SalesTimelineEvent, "tenant_id")
    assert hasattr(SalesTimelineEvent, "is_immutable")
