"""Advertisement model (Administration module).

Implements the extended ``advertisements`` table from the approved Administration
database design (``docs/02_Architecture/Database/07_Administration.md``).

The model is deliberately generic and data-driven: promotions are described by
rows, not hardcoded in templates, so new internal promotions — and future
external advertisers — can be added without code changes. All display logic
lives in :mod:`app.advertisements.services`.
"""

from __future__ import annotations

from app.extensions import db
from app.models.base import BaseModel
from app.models.enums import (
    AdvertisementMediaType,
    AdvertisementPlacement,
    AdvertisementType,
)
from app.models.mixins import PublicIdMixin, TimestampMixin

# Fallback duration (seconds) for a rewarded video when none is configured.
DEFAULT_VIDEO_DURATION = 20


def _enum_values(enum_cls):
    """Persist the ENUM member *values* (e.g. ``"banner"``) rather than names."""
    return [member.value for member in enum_cls]


class Advertisement(BaseModel, PublicIdMixin, TimestampMixin):
    """A reusable, placement-targeted advertisement or internal promotion."""

    __tablename__ = "advertisements"
    __table_args__ = (
        db.Index("ix_advertisements_created_at", "created_at"),
        # The hot query is "active ads for a placement, best first".
        db.Index(
            "ix_advertisements_placement_active",
            "placement",
            "is_active",
            "priority",
        ),
    )

    title = db.Column(db.String(150), nullable=False)
    description = db.Column(db.Text, nullable=True)
    advertisement_type = db.Column(
        db.Enum(AdvertisementType, values_callable=_enum_values, name="advertisement_type"),
        nullable=False,
        default=AdvertisementType.BANNER,
        index=True,
    )
    placement = db.Column(
        db.Enum(AdvertisementPlacement, values_callable=_enum_values, name="advertisement_placement"),
        nullable=False,
        default=AdvertisementPlacement.LANDING,
        index=True,
    )
    # Creative media type. IMAGE (default) keeps the original banner behaviour;
    # VIDEO renders an HTML5 <video> from ``media_url`` in the rewarded flow.
    media_type = db.Column(
        db.Enum(AdvertisementMediaType, values_callable=_enum_values, name="advertisement_media_type"),
        nullable=False,
        default=AdvertisementMediaType.IMAGE,
        server_default=AdvertisementMediaType.IMAGE.value,
    )
    # Source URL for a video creative (external or hosted). Images use image_path.
    media_url = db.Column(db.String(500), nullable=True)
    # Configured play length for a video ad, in seconds (unlock time is driven by
    # this, not the actual media length — see the rewarded modal).
    video_duration = db.Column(db.Integer, nullable=True)
    # Optional: internal promotions may render with a branded icon instead.
    image_path = db.Column(db.String(255), nullable=True)
    # Bootstrap icon class (e.g. "bi-stars") used as a fallback when no image.
    icon = db.Column(db.String(50), nullable=True)
    redirect_url = db.Column(db.String(500), nullable=True)
    is_active = db.Column(db.Boolean, nullable=False, default=True, index=True)
    # Admin-facing "Display Weight": the relative probability this advertisement is
    # chosen by the weighted-random selector — NOT an ordering key. Default 100 =
    # "normal"; equal weights give equal exposure. The column name is kept as
    # ``priority`` so no migration or data change is required.
    priority = db.Column(db.Integer, nullable=False, default=100, index=True)
    start_at = db.Column(db.DateTime, nullable=True)
    end_at = db.Column(db.DateTime, nullable=True)
    impression_count = db.Column(db.Integer, nullable=False, default=0)
    click_count = db.Column(db.Integer, nullable=False, default=0)

    @property
    def is_video(self) -> bool:
        """True when this ad should render as a playable video creative."""
        return self.media_type == AdvertisementMediaType.VIDEO and bool(self.media_url)

    @property
    def is_image(self) -> bool:
        """True for the classic image/banner (or icon-only) creative."""
        return not self.is_video

    @property
    def media_src(self) -> str | None:
        """The creative's source URL: the video URL for video ads, else the image."""
        return self.media_url if self.is_video else self.image_path

    @property
    def resolved_video_duration(self) -> int:
        """Configured video length in seconds, falling back to the default."""
        return self.video_duration or DEFAULT_VIDEO_DURATION

    def __repr__(self) -> str:
        return (
            f"<Advertisement id={self.id} placement={self.placement.value!r} "
            f"title={self.title!r}>"
        )
