"""CRM entities."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date
from decimal import Decimal

from .enums import ActivityState, LeadPriority, LeadState, LeadType
from .exceptions import InvalidActivityError, InvalidContactError, InvalidLeadError, InvalidStageError, InvalidTransitionError
from .value_objects import EmailAddress, LeadScore, PhoneNumber


@dataclass
class Contact:
    name: str
    email: EmailAddress | None = None
    phone: PhoneNumber | None = None
    company: str | None = None

    def __post_init__(self) -> None:
        if not isinstance(self.name, str) or not self.name.strip():
            raise InvalidContactError("contact name cannot be empty")
        if self.company is not None and (not isinstance(self.company, str) or not self.company.strip()):
            raise InvalidContactError("company must be a non-empty string when provided")


@dataclass
class Activity:
    subject: str
    due_date: date
    state: ActivityState = ActivityState.PLANNED
    note: str = ""

    def __post_init__(self) -> None:
        if not isinstance(self.subject, str) or not self.subject.strip():
            raise InvalidActivityError("activity subject cannot be empty")
        if not isinstance(self.due_date, date):
            raise InvalidActivityError("activity due_date must be a date")
        if not isinstance(self.state, ActivityState):
            raise InvalidActivityError("invalid activity state")
        if not isinstance(self.note, str):
            raise InvalidActivityError("activity note must be a string")

    def mark_done(self) -> None:
        self.state = ActivityState.DONE

    def cancel(self) -> None:
        self.state = ActivityState.CANCELLED


@dataclass(frozen=True)
class Stage:
    name: str
    sequence: int
    probability: Decimal = Decimal("0")
    is_won: bool = False
    is_lost: bool = False

    def __post_init__(self) -> None:
        if not isinstance(self.name, str) or not self.name.strip():
            raise InvalidStageError("stage name cannot be empty")
        if isinstance(self.sequence, bool) or not isinstance(self.sequence, int):
            raise InvalidStageError("stage sequence must be an integer")
        if self.sequence < 0:
            raise InvalidStageError("stage sequence cannot be negative")
        if not isinstance(self.probability, Decimal):
            raise InvalidStageError("stage probability must be a Decimal")
        if self.probability < 0 or self.probability > 1:
            raise InvalidStageError("stage probability must be between 0 and 1")
        if self.is_won and self.is_lost:
            raise InvalidStageError("a stage cannot be both won and lost")

    @property
    def is_terminal(self) -> bool:
        return self.is_won or self.is_lost


@dataclass
class Lead:
    name: str
    stage: Stage
    contact: Contact | None = None
    lead_type: LeadType = LeadType.LEAD
    state: LeadState = LeadState.NEW
    priority: LeadPriority = LeadPriority.NORMAL
    expected_revenue: Decimal = Decimal("0")
    score: LeadScore = field(default_factory=lambda: LeadScore(0))
    lost_reason: str | None = None
    activities: list[Activity] = field(default_factory=list)

    def __post_init__(self) -> None:
        if not isinstance(self.name, str) or not self.name.strip():
            raise InvalidLeadError("lead name cannot be empty")
        if not isinstance(self.stage, Stage):
            raise InvalidLeadError("lead stage must be a Stage instance")
        if self.contact is not None and not isinstance(self.contact, Contact):
            raise InvalidLeadError("lead contact must be a Contact instance or None")
        if not isinstance(self.lead_type, LeadType):
            raise InvalidLeadError("invalid lead type")
        if not isinstance(self.state, LeadState):
            raise InvalidLeadError("invalid lead state")
        if not isinstance(self.priority, LeadPriority):
            raise InvalidLeadError("invalid lead priority")
        if isinstance(self.expected_revenue, bool) or not isinstance(self.expected_revenue, Decimal):
            raise InvalidLeadError("expected revenue must be a Decimal")
        if self.expected_revenue < 0:
            raise InvalidLeadError("expected revenue cannot be negative")
        if not isinstance(self.score, LeadScore):
            raise InvalidLeadError("score must be a LeadScore instance")
        if self.lost_reason is not None and (not isinstance(self.lost_reason, str) or not self.lost_reason.strip()):
            raise InvalidLeadError("lost reason must be a non-empty string when provided")
        if any(not isinstance(activity, Activity) for activity in self.activities):
            raise InvalidLeadError("activities must contain Activity instances")

    def add_activity(self, activity: Activity) -> Activity:
        if not isinstance(activity, Activity):
            raise InvalidLeadError("activity must be an Activity instance")
        self.activities.append(activity)
        return activity

    @property
    def open_activities(self) -> list[Activity]:
        return [activity for activity in self.activities if activity.state == ActivityState.PLANNED]

    @property
    def forecast_revenue(self) -> Decimal:
        return self.expected_revenue * self.stage.probability

    def set_stage(self, stage: Stage, *, policy: "LeadStagePolicy | None" = None) -> None:
        if policy is None:
            from .policies import DefaultLeadStagePolicy

            policy = DefaultLeadStagePolicy()
        if not policy.can_move(self.stage, stage, lead=self):
            raise InvalidTransitionError(f"cannot move lead from {self.stage.name} to {stage.name}")
        self.stage = stage

    def qualify(self, stage: Stage, *, policy: "LeadStagePolicy | None" = None) -> None:
        self.lead_type = LeadType.OPPORTUNITY
        self.state = LeadState.QUALIFIED
        self.lost_reason = None
        self.set_stage(stage, policy=policy)

    def mark_won(self, stage: Stage, *, policy: "LeadStagePolicy | None" = None) -> None:
        self.lead_type = LeadType.OPPORTUNITY
        self.state = LeadState.WON
        self.lost_reason = None
        self.set_stage(stage, policy=policy)

    def mark_lost(self, reason: str, stage: Stage, *, policy: "LeadStagePolicy | None" = None) -> None:
        if not isinstance(reason, str) or not reason.strip():
            raise InvalidLeadError("lost reason cannot be empty")
        self.state = LeadState.LOST
        self.lost_reason = reason.strip()
        self.set_stage(stage, policy=policy)

    def complete_activity(self, activity: Activity) -> None:
        if not isinstance(activity, Activity):
            raise InvalidLeadError("activity must be an Activity instance")
        if activity not in self.activities:
            raise InvalidLeadError("activity is not attached to this lead")
        activity.mark_done()

    def cancel_activity(self, activity: Activity) -> None:
        if not isinstance(activity, Activity):
            raise InvalidLeadError("activity must be an Activity instance")
        if activity not in self.activities:
            raise InvalidLeadError("activity is not attached to this lead")
        activity.cancel()
