import sys

from flask_babel import gettext
from werkzeug.security import generate_password_hash, check_password_hash
from pritunl_dedicated.extensions import db
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from typing import Generator, Union, Tuple


class BaseTable(db.Model):
    __abstract__ = True
    updated = db.Column(db.DateTime, default=func.now(), onupdate=func.current_timestamp())
    created = db.Column(db.DateTime, default=func.now())


subscription_version_association_table = db.Table(
    'subscription_version_association',
    BaseTable.metadata,
    db.Column('subscription_id', db.Integer, db.ForeignKey('subscription.id')),
    db.Column('version_id', db.Integer, db.ForeignKey('version.id'))
)


class User(BaseTable):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), index=True, nullable=False)
    password = db.Column(db.String(255), nullable=False)
    first_name = db.Column(db.String(255), nullable=False)
    last_name = db.Column(db.String(255), nullable=False)

    def set_password(self, password):
        self.password = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password, password)

    def is_active(self):
        return True

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            if isinstance(self.id, str):
                return self.id
            elif isinstance(self.id, int):
                return self.id
            else:
                return self.id
        except AttributeError:
            raise NotImplementedError('No `id` attribute - override `get_id`')

    def __eq__(self, other):
        """
        Checks the equality of two `UserMixin` objects using `get_id`.
        """
        if isinstance(other, User):
            return self.get_id() == other.get_id()
        return NotImplemented

    def __ne__(self, other):
        """
        Checks the inequality of two `UserMixin` objects using `get_id`.
        """
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

    def jsonify(self):
        return {
            'id': self.id,
            'username': self.username,
            'password': None,
            'first_name': self.first_name,
            'last_name': self.last_name
        }

    if sys.version_info[0] != 2:  # pragma: no cover
        # Python 3 implicitly set __hash__ to None if we override __eq__
        # We set it back to its default implementation
        __hash__ = object.__hash__


class Version(BaseTable):
    __tablename__ = 'version'
    id = db.Column(db.Integer, primary_key=True)
    version_integer = db.Column(db.Integer, nullable=False)
    version_string = db.Column(db.String(255), nullable=False)
    subscriptions = relationship(
        "Subscription",
        secondary=subscription_version_association_table,
        back_populates="versions")
    subscription_checks = relationship(
        "SubscriptionCheck",
        order_by="SubscriptionCheck.id",
        backref="version",
        lazy='dynamic'
    )

    def jsonify(self):
        return {
            'id': self.id,
            'version_integer': self.version_integer,
            'version_string': self.version_string
        }


class SubscriptionPlan(BaseTable):
    __tablename__ = 'subscription_plan'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    subscriptions = relationship(
        "Subscription",
        order_by="Subscription.id",
        backref="subscription_plan",
        lazy='dynamic'
    )

    def jsonify(self):
        return {
            'id': self.id,
            'name': self.name
        }


class SubscriptionCheck(BaseTable):
    __tablename__ = 'subscription_check'
    id = db.Column(db.Integer, primary_key=True)
    subscription_id = db.Column(db.Integer, db.ForeignKey('subscription.id'), nullable=False)
    version_id = db.Column(db.Integer, db.ForeignKey('version.id'), nullable=True)
    ip_address = db.Column(db.String(255), nullable=False)
    version_integer = db.Column(db.String(255), nullable=True)
    application_id = db.Column(db.String(255), nullable=True)
    is_ok = db.Column(db.Boolean, nullable=False)
    status_message = db.Column(db.String(255), nullable=True)

    def jsonify(self):
        return {
            'id': self.id,
            'subscription_id': self.subscription_id,
            'version_id': self.version_id,
            'ip_address': self.ip_address,
            'version_integer': self.version_integer,
            'application_id': self.application_id,
            'is_ok': self.is_ok,
            'status_message': self.status_message
        }


class Subscription(BaseTable):
    __tablename__ = 'subscription'
    id = db.Column(db.Integer, primary_key=True)
    license_key = db.Column(db.String(255), nullable=False, index=True, unique=True)
    is_active = db.Column(db.Boolean, nullable=False)
    is_valid = db.Column(db.Boolean, nullable=False)
    is_last_check_ok = db.Column(db.Boolean, nullable=True)
    quantity = db.Column(db.Integer, nullable=True)
    amount = db.Column(db.Integer, nullable=True)
    period_end = db.Column(db.DateTime, nullable=True)
    trial_end = db.Column(db.DateTime, nullable=True)
    is_cancel_at_period_end = db.Column(db.Boolean, nullable=False)
    balance = db.Column(db.Integer, nullable=True)
    url_key = db.Column(db.String(255), nullable=True)

    subscription_plan_id = db.Column(db.Integer, db.ForeignKey('subscription_plan.id'), nullable=False)
    versions = relationship(
        "Version",
        secondary=subscription_version_association_table,
        back_populates="subscriptions")

    subscription_checks = relationship(
        "SubscriptionCheck",
        order_by="SubscriptionCheck.id",
        backref="SubscriptionCheck",
        lazy='dynamic'
    )

    def jsonify(self):
        return {
            'id': self.id,
            'license_key': self.license_key,
            'is_active': self.is_active,
            'is_valid': self.is_valid,
            'is_last_check_ok': self.is_last_check_ok,
            'quantity': self.quantity,
            'amount': self.amount,
            'period_end': self.period_end.isoformat() if self.period_end else None,
            'trial_end': self.trial_end.isoformat() if self.trial_end else None,
            'is_cancel_at_period_end': self.is_cancel_at_period_end,
            'balance': self.balance,
            'url_key': self.url_key,
            'subscription_plan': self.subscription_plan.jsonify(),
            'versions': [v.jsonify() for v in self.versions]
        }
