from datetime import date
from decimal import Decimal

import pytest

from . import (
    Activity,
    Contact,
    DefaultLeadStagePolicy,
    EmailAddress,
    InvalidLeadError,
    InvalidLeadScoreError,
    InvalidPhoneError,
    Lead,
    LeadPriority,
    LeadScoringService,
    LeadState,
    LeadType,
    LeadWorkflowService,
    PhoneNumber,
    Stage,
)
from .value_objects import LeadScore


def test_crm_value_objects_validate_and_normalize() -> None:
    assert EmailAddress("  USER@Example.com ").value == "user@example.com"
    assert PhoneNumber(" +84 (901) 234-567 ").value == "+84901234567"

    with pytest.raises(InvalidPhoneError):
        PhoneNumber("12")


def test_lead_scoring_and_workflow_update_state_and_score() -> None:
    qualification = Stage(name="Qualification", sequence=10, probability=Decimal("0.40"))
    proposal = Stage(name="Proposal", sequence=20, probability=Decimal("0.75"))
    contact = Contact(
        name="Alice Nguyen",
        email=EmailAddress("alice@example.com"),
        phone=PhoneNumber("+84901234567"),
        company="ACME",
    )
    lead = Lead(
        name="Website inquiry",
        stage=qualification,
        contact=contact,
        lead_type=LeadType.LEAD,
        priority=LeadPriority.HIGH,
        expected_revenue=Decimal("1500000"),
    )
    activity = Activity(subject="Call back", due_date=date(2026, 4, 5))

    workflow = LeadWorkflowService()
    workflow.add_activity(lead, activity)

    assert lead.score.value > 0
    assert lead.open_activities == [activity]

    workflow.qualify(lead, proposal)

    assert lead.state == LeadState.QUALIFIED
    assert lead.lead_type == LeadType.OPPORTUNITY
    assert lead.stage == proposal
    assert LeadScoringService().score(lead).value == lead.score.value


def test_lead_stage_policy_blocks_backwards_moves_from_terminal_stage() -> None:
    open_stage = Stage(name="Open", sequence=10, probability=Decimal("0.20"))
    won_stage = Stage(name="Won", sequence=90, probability=Decimal("1"), is_won=True)
    policy = DefaultLeadStagePolicy()
    lead = Lead(name="Deal", stage=won_stage)

    assert policy.can_move(open_stage, won_stage) is True
    assert policy.can_move(won_stage, open_stage, lead=lead) is False


def test_invalid_lead_score_rejected() -> None:
    with pytest.raises(InvalidLeadScoreError):
        LeadScore(101)

    with pytest.raises(InvalidLeadError):
        Lead(
            name="Deal",
            stage=Stage(name="Open", sequence=1),
            score="bad",  # type: ignore[arg-type]
        )

    with pytest.raises(InvalidLeadError):
        Lead(name="Deal", stage=Stage(name="Open", sequence=1), expected_revenue=Decimal("-1"))
