"""SQLAlchemy models — full schema for BulkGmailMailer.

All OAuth secrets are stored encrypted (Fernet). Sensitive columns:
  oauth_tokens.access_token / refresh_token
  gmail_accounts.client_secret (per-account imported config)
"""
from datetime import datetime, timezone
from sqlalchemy import (JSON, Boolean, Column, DateTime, Enum, Float, ForeignKey,
                        Index, Integer, LargeBinary, String, Text, UniqueConstraint)
from sqlalchemy.orm import DeclarativeBase, relationship

from app.core import encryption


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


class Base(DeclarativeBase):
    pass


class TimestampMixin:
    created_at = Column(DateTime(timezone=True), default=utcnow, nullable=False, index=True)
    updated_at = Column(DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False)


class User(Base, TimestampMixin):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String(320), unique=True, nullable=False, index=True)
    password_hash = Column(String(255), nullable=False)
    full_name = Column(String(200), default="")
    is_admin = Column(Boolean, default=False, nullable=False)
    is_active = Column(Boolean, default=True, nullable=False)
    last_login_at = Column(DateTime(timezone=True))

    gmail_accounts = relationship("GmailAccount", back_populates="user", cascade="all, delete-orphan")
    audit_logs = relationship("AuditLog", back_populates="user")


class OAuthClientConfig(Base, TimestampMixin):
    """An imported Google OAuth client configuration (from client_secret JSON).
    Secrets stored encrypted. Multiple configs may exist (e.g. per project)."""
    __tablename__ = "oauth_client_configs"
    id = Column(Integer, primary_key=True)
    name = Column(String(200), nullable=False)
    client_id = Column(String(300), nullable=False)
    client_secret_enc = Column(String(600), nullable=False)  # encrypted
    redirect_uri = Column(String(500), default="")
    project_id = Column(String(200), default="")
    is_active = Column(Boolean, default=True, nullable=False)
    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"))

    @property
    def client_secret(self) -> str:
        return encryption.decrypt(self.client_secret_enc)

    @client_secret.setter
    def client_secret(self, value: str) -> None:
        self.client_secret_enc = encryption.encrypt(value)


class GmailAccount(Base, TimestampMixin):
    __tablename__ = "gmail_accounts"
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
    email = Column(String(320), nullable=False)
    display_name = Column(String(200), default="")
    status = Column(String(30), default="pending")  # pending|authorized|expired|revoked|error
    sending_method = Column(String(20), default="api")  # api|smtp
    auth_provider = Column(String(60), default="google")
    google_user_id = Column(String(120), default="")
    # Per-account config (administrator-configurable)
    daily_limit = Column(Integer, default=200)          # provider/configured daily cap
    campaign_limit = Column(Integer, default=200)       # per-account per-campaign cap
    sends_today = Column(Integer, default=0)
    last_send_date = Column(DateTime(timezone=True))
    cooldown_until = Column(DateTime(timezone=True))
    last_success_at = Column(DateTime(timezone=True))
    last_error = Column(Text, default="")
    error_count = Column(Integer, default=0)
    token_expiry = Column(DateTime(timezone=True))
    # Imported client config reference (optional; may use server-level config)
    oauth_config_id = Column(Integer, ForeignKey("oauth_client_configs.id", ondelete="SET NULL"))
    # Encrypted SMTP OAuth2 credentials mirror (kept in oauth_tokens; this is metadata only)
    is_active = Column(Boolean, default=True, nullable=False)
    revoked_by_user = Column(Boolean, default=False)
    revoked_at = Column(DateTime(timezone=True))

    user = relationship("User", back_populates="gmail_accounts")
    # Eager-loaded (joined) so async endpoints/workers can read account.oauth_token
    # without triggering a lazy-load IO (which raises MissingGreenlet in async sessions).
    oauth_token = relationship("OAuthToken", back_populates="account", uselist=False,
                               cascade="all, delete-orphan", lazy="joined")
    oauth_config = relationship("OAuthClientConfig")

    __table_args__ = (UniqueConstraint("user_id", "email", name="uq_gmail_account_user_email"),
                      Index("ix_gmail_status", "status"),)

    def usage_summary(self):
        return {"daily_limit": self.daily_limit, "sends_today": self.sends_today,
                "campaign_limit": self.campaign_limit,
                "last_success_at": self.last_success_at.isoformat() if self.last_success_at else None,
                "errors": self.error_count, "status": self.status}


class OAuthToken(Base, TimestampMixin):
    __tablename__ = "oauth_tokens"
    id = Column(Integer, primary_key=True)
    account_id = Column(Integer, ForeignKey("gmail_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
    access_token_enc = Column(String(1200), nullable=False)  # encrypted
    refresh_token_enc = Column(String(1200), default="")     # encrypted; may be empty if not granted
    token_type = Column(String(50), default="Bearer")
    scope = Column(Text, default="")
    expires_at = Column(DateTime(timezone=True))
    granted_scopes = Column(Text, default="")

    account = relationship("GmailAccount", back_populates="oauth_token")

    @property
    def access_token(self) -> str:
        return encryption.decrypt(self.access_token_enc)

    @access_token.setter
    def access_token(self, value: str) -> None:
        self.access_token_enc = encryption.encrypt(value)

    @property
    def refresh_token(self) -> str:
        return encryption.decrypt(self.refresh_token_enc or "")

    @refresh_token.setter
    def refresh_token(self, value: str) -> None:
        self.refresh_token_enc = encryption.encrypt(value or "")

    def to_safe_dict(self):
        return {"id": self.id, "account_id": self.account_id, "token_type": self.token_type,
                "scope": self.scope, "expires_at": self.expires_at.isoformat() if self.expires_at else None,
                "has_refresh_token": bool(self.refresh_token_enc)}
