from __future__ import annotations

import secrets
from datetime import UTC, date, datetime, timedelta

from flask_login import UserMixin
from werkzeug.security import check_password_hash, generate_password_hash

from .extensions import db, login_manager


def utcnow() -> datetime:
    return datetime.now(UTC).replace(tzinfo=None)


class TimestampMixin:
    created_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    updated_at = db.Column(db.DateTime, nullable=False, default=utcnow, onupdate=utcnow)


class User(UserMixin, TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    email = db.Column(db.String(255), nullable=False, unique=True, index=True)
    password_hash = db.Column(db.String(255), nullable=False)
    is_admin = db.Column(db.Boolean, nullable=False, default=False)
    avatar_path = db.Column(db.String(255), nullable=True)
    calendar_feed_token = db.Column(db.String(255), nullable=True, unique=True, index=True)
    notification_task_due_enabled = db.Column(db.Boolean, nullable=False, default=True)
    notification_monthly_winner_enabled = db.Column(db.Boolean, nullable=False, default=True)
    notification_badge_enabled = db.Column(db.Boolean, nullable=False, default=True)

    assigned_task_templates = db.relationship(
        "TaskTemplate",
        foreign_keys="TaskTemplate.default_assigned_user_id",
        backref="default_assigned_user",
        lazy=True,
    )
    secondary_assigned_task_templates = db.relationship(
        "TaskTemplate",
        foreign_keys="TaskTemplate.default_assigned_user_secondary_id",
        backref="default_assigned_user_secondary",
        lazy=True,
    )
    assigned_tasks = db.relationship(
        "TaskInstance",
        foreign_keys="TaskInstance.assigned_user_id",
        backref="assigned_user",
        lazy=True,
    )
    secondary_assigned_tasks = db.relationship(
        "TaskInstance",
        foreign_keys="TaskInstance.assigned_user_secondary_id",
        backref="assigned_user_secondary",
        lazy=True,
    )
    completed_tasks = db.relationship(
        "TaskInstance",
        foreign_keys="TaskInstance.completed_by_user_id",
        backref="completed_by_user",
        lazy=True,
    )
    subscriptions = db.relationship("PushSubscription", backref="user", lazy=True, cascade="all, delete-orphan")
    created_quick_wins = db.relationship("QuickWin", backref="created_by_user", lazy=True)
    awarded_badges = db.relationship(
        "UserBadge",
        backref="user",
        lazy=True,
        cascade="all, delete-orphan",
        order_by="desc(UserBadge.awarded_at)",
    )

    def set_password(self, password: str) -> None:
        self.password_hash = generate_password_hash(password)

    def check_password(self, password: str) -> bool:
        return check_password_hash(self.password_hash, password)

    def ensure_calendar_feed_token(self) -> str:
        if not self.calendar_feed_token:
            self.calendar_feed_token = secrets.token_urlsafe(32)
        return self.calendar_feed_token

    @property
    def display_avatar(self) -> str:
        return self.avatar_path or "images/avatars/default.svg"

    def __repr__(self) -> str:
        return f"<User {self.email}>"


@login_manager.user_loader
def load_user(user_id: str) -> User | None:
    return db.session.get(User, int(user_id))


class TaskTemplate(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(160), nullable=False)
    description = db.Column(db.Text, nullable=True)
    default_points = db.Column(db.Integer, nullable=False, default=10)
    default_assigned_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
    default_assigned_user_secondary_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
    recurrence_interval_value = db.Column(db.Integer, nullable=True)
    recurrence_interval_unit = db.Column(db.String(20), nullable=False, default="none")
    active = db.Column(db.Boolean, nullable=False, default=True)

    instances = db.relationship("TaskInstance", backref="task_template", lazy=True, cascade="all, delete-orphan")

    @property
    def recurrence_label(self) -> str:
        if self.recurrence_interval_unit == "none" or not self.recurrence_interval_value:
            return "Einmalig"
        units = {
            "days": ("Tag", "Tage"),
            "weeks": ("Woche", "Wochen"),
            "months": ("Monat", "Monate"),
        }
        singular, plural = units.get(self.recurrence_interval_unit, (self.recurrence_interval_unit, self.recurrence_interval_unit))
        unit_label = singular if self.recurrence_interval_value == 1 else plural
        return f"Alle {self.recurrence_interval_value} {unit_label}"


class QuickWin(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(160), nullable=False, index=True)
    effort = db.Column(db.String(40), nullable=False, index=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    created_by_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    sort_order = db.Column(db.Integer, nullable=False, default=0, index=True)


class TaskInstance(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    task_template_id = db.Column(db.Integer, db.ForeignKey("task_template.id"), nullable=False, index=True)
    title = db.Column(db.String(160), nullable=False)
    description = db.Column(db.Text, nullable=True)
    assigned_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
    assigned_user_secondary_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
    due_date = db.Column(db.Date, nullable=False, index=True)
    status = db.Column(db.String(20), nullable=False, default="open", index=True)
    completed_at = db.Column(db.DateTime, nullable=True, index=True)
    completed_by_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
    points_awarded = db.Column(db.Integer, nullable=False, default=10)

    @property
    def is_completed(self) -> bool:
        return self.completed_at is not None

    @property
    def assigned_users(self) -> list[User]:
        users: list[User] = []
        if self.assigned_user:
            users.append(self.assigned_user)
        if self.assigned_user_secondary and self.assigned_user_secondary.id not in {user.id for user in users}:
            users.append(self.assigned_user_secondary)
        return users

    @property
    def assigned_user_ids(self) -> list[int]:
        return [user.id for user in self.assigned_users]

    @property
    def is_shared_assignment(self) -> bool:
        return self.assigned_user_id is not None and self.assigned_user_secondary_id is not None

    @property
    def assignee_label(self) -> str:
        if not self.assigned_users:
            return "Ohne Person"
        return " & ".join(user.name for user in self.assigned_users)

    def compute_status(self, reference_date: date | None = None) -> str:
        reference_date = reference_date or date.today()
        if self.completed_at:
            return "completed"
        if self.due_date < reference_date:
            return "overdue"
        if self.due_date == reference_date:
            return "due_today"
        if self.due_date == reference_date + timedelta(days=1):
            return "due_tomorrow"
        if self.due_date == reference_date + timedelta(days=2):
            return "due_day_after_tomorrow"
        return "open"

    @property
    def status_label(self) -> str:
        labels = {
            "open": "Offen",
            "due_today": "Heute fällig",
            "due_tomorrow": "Morgen fällig",
            "due_day_after_tomorrow": "Übermorgen fällig",
            "overdue": "Überfällig",
            "completed": "Erledigt",
        }
        return labels.get(self.status, "Offen")


class MonthlyScoreSnapshot(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    year = db.Column(db.Integer, nullable=False, index=True)
    month = db.Column(db.Integer, nullable=False, index=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    total_points = db.Column(db.Integer, nullable=False, default=0)
    completed_tasks_count = db.Column(db.Integer, nullable=False, default=0)
    rank = db.Column(db.Integer, nullable=False, default=1)
    created_at = db.Column(db.DateTime, nullable=False, default=utcnow)

    user = db.relationship("User", backref="monthly_snapshots")

    __table_args__ = (db.UniqueConstraint("year", "month", "user_id", name="uq_snapshot_month_user"),)


class PushSubscription(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    endpoint = db.Column(db.Text, nullable=False, unique=True)
    p256dh = db.Column(db.Text, nullable=False)
    auth = db.Column(db.Text, nullable=False)


class NotificationLog(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    type = db.Column(db.String(80), nullable=False, index=True)
    payload = db.Column(db.Text, nullable=False)
    sent_at = db.Column(db.DateTime, nullable=False, default=utcnow, index=True)

    user = db.relationship("User", backref="notification_logs")


class BadgeDefinition(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(80), nullable=False, unique=True, index=True)
    name = db.Column(db.String(120), nullable=False)
    description = db.Column(db.String(255), nullable=False)
    icon_name = db.Column(db.String(80), nullable=False, default="sparkles")
    trigger_type = db.Column(db.String(80), nullable=False)
    threshold = db.Column(db.Integer, nullable=False, default=1)
    bonus_points = db.Column(db.Integer, nullable=False, default=0)
    active = db.Column(db.Boolean, nullable=False, default=True)

    user_badges = db.relationship("UserBadge", backref="badge_definition", lazy=True, cascade="all, delete-orphan")


class UserBadge(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    badge_definition_id = db.Column(db.Integer, db.ForeignKey("badge_definition.id"), nullable=False, index=True)
    awarded_at = db.Column(db.DateTime, nullable=False, default=utcnow, index=True)
    context = db.Column(db.Text, nullable=True)

    __table_args__ = (db.UniqueConstraint("user_id", "badge_definition_id", name="uq_user_badge_once"),)


class AppSetting(TimestampMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(120), nullable=False, unique=True, index=True)
    value = db.Column(db.String(255), nullable=False)
