from datetime import date
from enum import Enum

from common.extensions import db
from flask_security import UserMixin, RoleMixin
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
import sqlalchemy as sa

from common.models.entitlements import License


class Tenant(db.Model):
    """Tenant model"""

    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())

    # company Information
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(50), unique=True, nullable=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    website = db.Column(db.String(255), nullable=True)
    timezone = db.Column(db.String(50), nullable=True, default='UTC')
    type = db.Column(db.String(20), nullable=True, server_default='Active')

   # Entitlements
    currency = db.Column(db.String(20), nullable=True)
    storage_dirty = db.Column(db.Boolean, nullable=True, default=False)
    default_tenant_make_id = db.Column(db.Integer, db.ForeignKey('public.tenant_make.id'), nullable=True)

    # Relations
    users = db.relationship('User', backref='tenant')
    domains = db.relationship('TenantDomain', backref='tenant')
    licenses = db.relationship('License', back_populates='tenant')
    license_usages = db.relationship('LicenseUsage', backref='tenant')
    tenant_makes = db.relationship('TenantMake', backref='tenant', foreign_keys='TenantMake.tenant_id')
    default_tenant_make = db.relationship('TenantMake', foreign_keys=[default_tenant_make_id], uselist=False)

    @property
    def current_license(self):
        today = date.today()
        return License.query.filter(
            License.tenant_id == self.id,
            License.start_date <= today,
            (License.end_date.is_(None) | (License.end_date >= today))
        ).order_by(License.start_date.desc()).first()

    def __repr__(self):
        return f"<Tenant {self.id}: {self.name}>"

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'website': self.website,
            'timezone': self.timezone,
            'type': self.type,
            'currency': self.currency,
            'default_tenant_make_id': self.default_tenant_make_id,
        }


class Role(db.Model, RoleMixin):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))


class RolesUsers(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    user_id = db.Column(db.Integer(), db.ForeignKey('public.user.id', ondelete='CASCADE'), primary_key=True)
    role_id = db.Column(db.Integer(), db.ForeignKey('public.role.id', ondelete='CASCADE'), primary_key=True)


class User(db.Model, UserMixin):
    """User model"""

    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())

    # User Information
    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    user_name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(255), unique=True, nullable=False)
    password = db.Column(db.String(255), nullable=True)
    first_name = db.Column(db.String(80), nullable=False)
    last_name = db.Column(db.String(80), nullable=False)
    active = db.Column(db.Boolean)
    fs_uniquifier = db.Column(db.String(255), unique=True, nullable=False)
    confirmed_at = db.Column(db.DateTime, nullable=True)
    valid_to = db.Column(db.Date, nullable=True)
    is_primary_contact = db.Column(db.Boolean, nullable=True, default=False)
    is_financial_contact = db.Column(db.Boolean, nullable=True, default=False)

    # Security Trackable Information
    last_login_at = db.Column(db.DateTime, nullable=True)
    current_login_at = db.Column(db.DateTime, nullable=True)
    last_login_ip = db.Column(db.String(255), nullable=True)
    current_login_ip = db.Column(db.String(255), nullable=True)
    login_count = db.Column(db.Integer, nullable=False, default=0)

    # Relations
    roles = db.relationship('Role', secondary=RolesUsers.__table__, backref=db.backref('users', lazy='dynamic'))

    def __repr__(self):
        return '<User %r>' % self.user_name

    def has_roles(self, *args):
        return any(role.name in args for role in self.roles)

class TenantDomain(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    # Originally, domain was required to be unique.
    # However, several tenants can run from the same domain (e.g. for demo purposes,
    # but also internal and external chat clients.
    domain = db.Column(db.String(255), nullable=False)
    valid_to = db.Column(db.Date, nullable=True)

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=False)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'))

    def __repr__(self):
        return f"<TenantDomain {self.id}: {self.domain}>"


class TenantProject(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    name = db.Column(db.String(50), nullable=False)
    description = db.Column(db.Text, nullable=True)
    services = db.Column(ARRAY(sa.String(50)), nullable=False)
    encrypted_api_key = db.Column(db.String(500), nullable=True)
    visual_api_key = db.Column(db.String(20), nullable=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    responsible_email = db.Column(db.String(255), nullable=True)

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'))

    # Relations
    tenant = db.relationship('Tenant', backref='projects')

    def __repr__(self):
        return f"<TenantProject {self.id}: {self.name}>"


class TenantMake(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    name = db.Column(db.String(50), nullable=False, unique=True)
    description = db.Column(db.Text, nullable=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    website = db.Column(db.String(255), nullable=True)
    logo_url = db.Column(db.String(255), nullable=True)
    default_language = db.Column(db.String(2), nullable=True)
    allowed_languages = db.Column(ARRAY(sa.String(2)), nullable=True)

    # Chat customisation options
    chat_customisation_options = db.Column(JSONB, nullable=True)

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'))

    def __repr__(self):
        return f"<TenantMake {self.id} for tenant {self.tenant_id}: {self.name}>"

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'active': self.active,
            'website': self.website,
            'logo_url': self.logo_url,
            'chat_customisation_options': self.chat_customisation_options,
            'allowed_languages': self.allowed_languages,
            'default_language': self.default_language,
        }


class Partner(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False, unique=True)
    code = db.Column(db.String(50), unique=True, nullable=False)

    # Basic information
    logo_url = db.Column(db.String(255), nullable=True)
    active = db.Column(db.Boolean, default=True)

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)

    # Relationships
    services = db.relationship('PartnerService', back_populates='partner')
    tenant = db.relationship('Tenant', backref=db.backref('partner', uselist=False))

    def to_dict(self):
        services_info = []
        for service in self.services:
            services_info.append({
                'id': service.id,
                'name': service.name,
                'description': service.description,
                'type': service.type,
                'type_version': service.type_version,
                'active': service.active,
                'configuration': service.configuration,
                'permissions': service.permissions,
            })
        return {
            'id': self.id,
            'tenant_id': self.tenant_id,
            'code': self.code,
            'logo_url': self.logo_url,
            'active': self.active,
            'name': self.tenant.name,
            'services': services_info
        }


class PartnerService(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    id = db.Column(db.Integer, primary_key=True)
    partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=False)

    # Basic info
    name = db.Column(db.String(50), nullable=False)
    description = db.Column(db.Text, nullable=True)

    # Service type with versioning (similar to your specialist/retriever pattern)
    type = db.Column(db.String(50), nullable=False)  # REFERRAL, KNOWLEDGE, SPECIALIST, IMPLEMENTATION, WHITE_LABEL
    type_version = db.Column(db.String(20), nullable=False, default="1.0.0")

    # Status
    active = db.Column(db.Boolean, default=True)

    # Dynamic configuration specific to this service - using JSONB like your other models
    configuration = db.Column(db.JSON, nullable=True)
    permissions = db.Column(db.JSON, nullable=True)

    # For services that need to track shared resources
    system_metadata = db.Column(db.JSON, nullable=True)
    user_metadata = db.Column(db.JSON, nullable=True)

    # Versioning Information
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)

    # Relationships
    partner = db.relationship('Partner', back_populates='services')
    license_tiers = db.relationship('PartnerServiceLicenseTier', back_populates='partner_service')


class PartnerTenant(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    partner_service_id = db.Column(db.Integer, db.ForeignKey('public.partner_service.id'), primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), primary_key=True)

    # JSONB for flexible configuration specific to this relationship
    configuration = db.Column(db.JSON, nullable=True)

    # Tracking
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)


class TenantConsent(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}
    id = db.Column(db.Integer, primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=True)
    partner_service_id = db.Column(db.Integer, db.ForeignKey('public.partner_service.id'), nullable=True)
    user_id = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=False)
    consent_type = db.Column(db.String(50), nullable=False)
    consent_date = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    consent_version = db.Column(db.String(20), nullable=False, default="1.0.0")
    consent_data = db.Column(db.JSON, nullable=False)

    # Tracking
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)


class ConsentVersion(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}
    id = db.Column(db.Integer, primary_key=True)
    consent_type = db.Column(db.String(50), nullable=False)
    consent_version = db.Column(db.String(20), nullable=False)
    consent_valid_from = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    consent_valid_to = db.Column(db.DateTime, nullable=True)

    # Tracking
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)


class ConsentStatus(str, Enum):
    CONSENTED = 'CONSENTED'
    NOT_CONSENTED = 'NOT_CONSENTED'
    RENEWAL_REQUIRED = 'RENEWAL_REQUIRED'
    CONSENT_EXPIRED = 'CONSENT_EXPIRED'
    UNKNOWN_CONSENT_VERSION = 'UNKNOWN_CONSENT_VERSION'

class SpecialistMagicLinkTenant(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    magic_link_code = db.Column(db.String(55), primary_key=True)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)


class TranslationCache(db.Model):
    __bind_key__ = 'public'
    __table_args__ = {'schema': 'public'}

    cache_key = db.Column(db.String(16), primary_key=True)
    source_text = db.Column(db.Text, nullable=False)
    translated_text = db.Column(db.Text, nullable=False)
    source_language = db.Column(db.String(2), nullable=True)
    target_language = db.Column(db.String(2), nullable=False)
    context = db.Column(db.Text, nullable=True)

    # Translation cost
    prompt_tokens = db.Column(db.Integer, nullable=False)
    completion_tokens = db.Column(db.Integer, nullable=False)

    # Tracking
    created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
    created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
    updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
    updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)

    last_used_at = db.Column(db.DateTime, nullable=True)


class PartnerRAGRetriever(db.Model):
    __bind_key__ = 'public'
    __table_args__ = (
        db.PrimaryKeyConstraint('tenant_id', 'retriever_id'),
        db.UniqueConstraint('partner_id', 'tenant_id', 'retriever_id'),
        {'schema': 'public'},
    )

    partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=False)
    tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
    retriever_id = db.Column(db.Integer, nullable=False)
