"""Campaign engine: materialization, rotation, sequencing, assignment.

The engine generates per-recipient values (invoice number, amount, shipping, subject,
template, phone, sender account) with collision-checked uniqueness and full audit data.
"""
import random
from datetime import date, datetime, timedelta, timezone
from typing import List, Optional, Sequence

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import (Campaign, CampaignRecipient, EmailTemplate, GmailAccount,
                        SubjectLine, CompanyPhoneNumber, Recipient, SuppressionEntry)
from app.services.variable_engine import VariableEngine, GeneratedValues
from app.core.config import settings


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):
    from app.models import ApplicationSettings
    row = (await db.execute(select(ApplicationSettings).where(ApplicationSettings.key == key))).scalar_one_or_none()
    if row is not None:
        return row.value
    return default
