def utcnow():
    return datetime.now(timezone.utc)


class RotationAssigner:
    """Assigns sender accounts per message: sequential or weighted, with limits & cooldown."""

    def __init__(self, accounts: list, strategy: str, weights: dict = None, max_per_account: int = 200):
        self.accounts = [a for a in accounts if a.status == "authorized" and a.is_active]
        self.strategy = strategy if strategy in ("sequential", "weighted") else "sequential"
        self.weights = weights or {}
        self.max_per_account = max_per_account
        self.usage = {a.id: 0 for a in self.accounts}
        self.cooling = {a.id: False for a in self.accounts}
        self._seq_index = 0
        self._pool = []
        self._pool_pos = 0

    def _available(self):
        avail = []
        for a in self.accounts:
            if self.usage[a.id] >= self.max_per_account:
                continue
            if a.cooldown_until and a.cooldown_until > utcnow():
                continue
            avail.append(a)
        return avail

    def next_account(self, account_id=None):
        """Return next account or None if all at limit/cooldown."""
        if account_id is not None:
            for a in self.accounts:
                if a.id == account_id:
                    self.usage[a.id] += 1
                    return a
        avail = self._available()
        if not avail:
            return None
        if self.strategy == "weighted":
            if not self._pool:
                pool = []
                for a in avail:
                    w = int(self.weights.get(str(a.id), 1))
                    pool.extend([a] * max(1, w))
                random.shuffle(pool)
                self._pool = pool
                self._pool_pos = 0
            while self._pool and self._pool_pos < len(self._pool):
                a = self._pool[self._pool_pos]
                self._pool_pos += 1
                if self.usage[a.id] < self.max_per_account and not (
                        a.cooldown_until and a.cooldown_until > utcnow()):
                    self.usage[a.id] += 1
                    return a
            self._pool = []
            return self.next_account()
        # sequential
        for _ in range(len(self.accounts)):
            a = self.accounts[self._seq_index % len(self.accounts)]
            self._seq_index += 1
            if self.usage[a.id] < self.max_per_account and not (
                    a.cooldown_until and a.cooldown_until > utcnow()):
                self.usage[a.id] += 1
                return a
        return None

    def status(self):
        return {"accounts": [{"id": a.id, "email": a.email, "usage": self.usage[a.id],
                              "limit": self.max_per_account} for a in self.accounts]}


class SubjectSelector:
    def __init__(self, subjects: List[SubjectLine], strategy: str = "round_robin"):
        self.subjects = subjects
        self.strategy = strategy if strategy in ("round_robin", "random", "fixed_first") else "round_robin"
        self._idx = 0

    def next_subject(self) -> Optional[SubjectLine]:
        if not self.subjects:
            return None
        if self.strategy == "random":
            return random.choice(self.subjects)
        if self.strategy == "fixed_first":
            return self.subjects[0]
        s = self.subjects[self._idx % len(self.subjects)]
        self._idx += 1
        return s


class TemplateSequencer:
    """Template 1 -> recipient 1, template 2 -> recipient 2 ... then repeat/stop/random."""
    def __init__(self, templates: List[EmailTemplate], mode: str = "repeat"):
        self.templates = templates
        self.mode = mode if mode in ("repeat", "stop", "random") else "repeat"
        self._idx = 0
        self._stopped = False

    def next_template(self) -> Optional[EmailTemplate]:
        if not self.templates:
            return None
        if self.mode == "random":
            return random.choice(self.templates)
        if self.mode == "stop":
            if self._idx >= len(self.templates):
                self._stopped = True
                return None
            t = self.templates[self._idx]
            self._idx += 1
            return t
        t = self.templates[self._idx % len(self.templates)]
        self._idx += 1
        return t

    @property
    def stopped(self):
        return self._stopped


class PhoneSelector:
    def __init__(self, phones: List[CompanyPhoneNumber], mode: str = "round_robin"):
        self.phones = [p for p in phones if p.is_active]
        self.mode = mode if mode in ("round_robin", "random", "fixed") else "round_robin"
        self._idx = 0

    def next_phone(self) -> str:
        if not self.phones:
            return ""
        if self.mode == "random":
            return random.choice(self.phones).phone
        if self.mode == "fixed":
            return self.phones[0].phone
        p = self.phones[self._idx % len(self.phones)]
        self._idx += 1
        return p.phone


async def get_setting(db: AsyncSession, key: str, default):
