"""FastAPI dependencies: DB session, current user (JWT via cookie or bearer), CSRF, rate limiting."""
from typing import Optional

from fastapi import Depends, Header, Request, Response, Cookie, HTTPException, status
from fastapi.security.utils import get_authorization_scheme_param
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.core.security import decode_token, rate_limit
from app.db.session import AsyncSessionLocal


async def get_db() -> AsyncSession:
    async with AsyncSessionLocal() as session:
        yield session


async def get_current_user(request: Request, response: Response,
                           authorization: Optional[str] = Header(None),
                           mailer_session: Optional[str] = Cookie(None),
                           db: AsyncSession = Depends(get_db)) -> "User":  # noqa: F821
    """Authenticate via JWT in HttpOnly cookie (web UI) or Authorization header (API/tests)."""
    from app.models import User
    token = None
    if authorization:
        scheme, param = get_authorization_scheme_param(authorization)
        if scheme.lower() == "bearer":
            token = param
    if not token and mailer_session:
        token = mailer_session

    if not token:
        raise HTTPException(status_code=401, detail="Not authenticated")

    payload = decode_token(token)
    if not payload:
        raise HTTPException(status_code=401, detail="Invalid or expired session")

    user = await db.get(User, int(payload["sub"]))
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="User not found or inactive")
    return user


async def get_optional_user(request: Request, db: AsyncSession = Depends(get_db)):
    from app.models import User
    token = request.cookies.get(settings.SESSION_COOKIE_NAME)
    authz = request.headers.get("authorization", "")
    if authz.lower().startswith("bearer "):
        token = authz[7:]
    if not token:
        return None
    payload = decode_token(token)
    if not payload:
        return None
    return await db.get(User, int(payload["sub"]))


def require_csrf(request: Request, x_csrf_token: Optional[str] = Header(None)) -> None:
===MSG BUILDER===
26:def build_mime_message(
27-    from_email: str,
28-    to_email: str,
29-    subject: str,
30-    html_body: str = "",
31-    text_body: str = "",
32-    attachments: Optional[list] = None,   # list of {filename, content(bytes), mime_type}
33-    unsubscribe_url: str = "",
34-    unsubscribe_mailto: str = "",
35-) -> MIMEMultipart:
36-    """Build a real MIME multipart message. From header always equals the authenticated account."""
37-    attachments = attachments or []
38-    if html_body and text_body:
39-        msg = MIMEMultipart("alternative")
40-    else:
41-        msg = MIMEMultipart("mixed")
42-    msg["From"] = from_email  # MUST be the authenticated Gmail account (never spoofed)
43-    msg["To"] = to_email
44-    msg["Subject"] = subject
45-    msg["Date"] = formatdate(localtime=True)
46-    msg["Message-ID"] = make_msgid()
===CAMPAIGN MODEL===
class Campaign(Base, TimestampMixin):
    __tablename__ = "campaigns"
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
    name = Column(String(250), nullable=False)
    status = Column(String(30), default="draft")  # draft|queued|running|paused|completed|cancelled|failed
    # selections
    recipient_list_id = Column(Integer, ForeignKey("recipient_lists.id", ondelete="SET NULL"))
    subject_set_ids = Column(JSON, default=list)   # list of subject line ids
    template_ids = Column(JSON, default=list)      # ordered template ids for sequencing
    message_type = Column(String(40), default="html")
    # attachment behaviour
    attach_pdf = Column(Boolean, default=False)
    attach_pdf_template_id = Column(Integer, ForeignKey("html_templates.id", ondelete="SET NULL"))
    attachment_id = Column(Integer, ForeignKey("attachments.id", ondelete="SET NULL"))
    attach_mode = Column(String(40), default="none")  # none|pdf|file|image
    # dynamic variables config
    variable_config = Column(JSON, default=dict)   # invoice date, due date, ranges etc.
    # rotation / limits
    account_ids = Column(JSON, default=list)
    rotation_strategy = Column(String(30), default="sequential")  # sequential|weighted
    rotation_weights = Column(JSON, default=dict)  # account_id -> weight
    subject_rotation = Column(String(30), default="round_robin")  # round_robin|random|fixed_first
    template_sequence_mode = Column(String(30), default="repeat")  # repeat|stop|random
    phone_selection = Column(String(30), default="round_robin")   # round_robin|random|fixed
    max_per_account = Column(Integer, default=200)
    send_interval_seconds = Column(Float, default=1.0)
    rate_per_minute = Column(Integer, default=60)
    max_retries = Column(Integer, default=3)
    retry_delay_seconds = Column(Integer, default=60)
    # counters
    total_recipients = Column(Integer, default=0)
    sent_count = Column(Integer, default=0)
    failed_count = Column(Integer, default=0)
    skipped_count = Column(Integer, default=0)
    suppressed_count = Column(Integer, default=0)
    bounced_count = Column(Integer, default=0)
    queued_count = Column(Integer, default=0)
    started_at = Column(DateTime(timezone=True))
    finished_at = Column(DateTime(timezone=True))
    preview_sample_size = Column(Integer, default=5)
    # compliance
    unsubscribe_enabled = Column(Boolean, default=True, nullable=False)

    recipient_list = relationship("RecipientList")
    send_jobs = relationship("SendJob", back_populates="campaign", cascade="all, delete-orphan")


class CampaignRecipient(Base, TimestampMixin):
    """Materialized campaign×recipient rows with generated values & audit info."""
    __tablename__ = "campaign_recipients"
    id = Column(Integer, primary_key=True)
    campaign_id = Column(Integer, ForeignKey("campaigns.id", ondelete="CASCADE"), nullable=False, index=True)
