import traceback
from flask import current_app
from urllib.parse import urljoin

from ..lib import utils
from .base import db
from .setting import Setting
from .user import User
from .account_user import AccountUser


class Account(db.Model):
    __tablename__ = 'account'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(40), index=True, unique=True, nullable=False)
    description = db.Column(db.String(128))
    contact = db.Column(db.String(128))
    mail = db.Column(db.String(128))
    domains = db.relationship("Domain", back_populates="account")
    apikeys = db.relationship("ApiKey",
                              secondary="apikey_account",
                              back_populates="accounts")

    def __init__(self, name=None, description=None, contact=None, mail=None):
        self.name = name
        self.description = description
        self.contact = contact
        self.mail = mail

        # PDNS configs
        self.PDNS_STATS_URL = Setting().get('pdns_api_url')
        self.PDNS_API_KEY = Setting().get('pdns_api_key')
        self.PDNS_VERSION = Setting().get('pdns_version')
        self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)

        if self.name is not None:
            self.name = ''.join(c for c in self.name.lower()
                                if c in "abcdefghijklmnopqrstuvwxyz0123456789")

    def __repr__(self):
        return '<Account {0}r>'.format(self.name)

    def get_name_by_id(self, account_id):
        """
        Convert account_id to account_name
        """
        account = Account.query.filter(Account.id == account_id).first()
        if account is None:
            return ''

        return account.name

    def get_id_by_name(self, account_name):
        """
        Convert account_name to account_id
        """
        # Skip actual database lookup for empty queries
        if account_name is None or account_name == "":
            return None

        account = Account.query.filter(Account.name == account_name).first()
        if account is None:
            return None

        return account.id

    def create_account(self):
        """
        Create a new account
        """
        # Sanity check - account name
        if self.name == "":
            return {'status': False, 'msg': 'No account name specified'}

        # check that account name is not already used
        account = Account.query.filter(Account.name == self.name).first()
        if account:
            return {'status': False, 'msg': 'Account already exists'}

        db.session.add(self)
        db.session.commit()
        return {'status': True, 'msg': 'Account created successfully'}

    def update_account(self):
        """
        Update an existing account
        """
        # Sanity check - account name
        if self.name == "":
            return {'status': False, 'msg': 'No account name specified'}

        # read account and check that it exists
        account = Account.query.filter(Account.name == self.name).first()
        if not account:
            return {'status': False, 'msg': 'Account does not exist'}

        account.description = self.description
        account.contact = self.contact
        account.mail = self.mail

        db.session.commit()
        return {'status': True, 'msg': 'Account updated successfully'}

    def delete_account(self, commit=True):
        """
        Delete an account
        """
        # unassociate all users first
        self.grant_privileges([])

        try:
            Account.query.filter(Account.name == self.name).delete()
            if commit:
                db.session.commit()
            return True
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot delete account {0} from DB. DETAIL: {1}'.format(
                    self.name, e))
            return False

    def get_user(self):
        """
        Get users (id) associated with this account
        """
        user_ids = []
        query = db.session.query(
            AccountUser,
            Account).filter(User.id == AccountUser.user_id).filter(
                Account.id == AccountUser.account_id).filter(
                    Account.name == self.name).all()
        for q in query:
            user_ids.append(q[0].user_id)
        return user_ids

    def grant_privileges(self, new_user_list):
        """
        Reconfigure account_user table
        """
        account_id = self.get_id_by_name(self.name)

        account_user_ids = self.get_user()
        new_user_ids = [
            u.id
            for u in User.query.filter(User.username.in_(new_user_list)).all()
        ] if new_user_list else []

        removed_ids = list(set(account_user_ids).difference(new_user_ids))
        added_ids = list(set(new_user_ids).difference(account_user_ids))

        try:
            for uid in removed_ids:
                AccountUser.query.filter(AccountUser.user_id == uid).filter(
                    AccountUser.account_id == account_id).delete()
                db.session.commit()
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot revoke user privileges on account {0}. DETAIL: {1}'.
                format(self.name, e))

        try:
            for uid in added_ids:
                au = AccountUser(account_id, uid)
                db.session.add(au)
                db.session.commit()
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot grant user privileges to account {0}. DETAIL: {1}'.
                format(self.name, e))

    def revoke_privileges_by_id(self, user_id):
        """
        Remove a single user from privilege list based on user_id
        """
        new_uids = [u for u in self.get_user() if u != user_id]
        users = []
        for uid in new_uids:
            users.append(User(id=uid).get_user_info_by_id().username)

        self.grant_privileges(users)

    def add_user(self, user):
        """
        Add a single user to Account by User
        """
        try:
            au = AccountUser(self.id, user.id)
            db.session.add(au)
            db.session.commit()
            return True
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot add user privileges on account {0}. DETAIL: {1}'.
                format(self.name, e))
            return False

    def remove_user(self, user):
        """
        Remove a single user from Account by User
        """
        # TODO: This func is currently used by SAML feature in a wrong way. Fix it
        try:
            AccountUser.query.filter(AccountUser.user_id == user.id).filter(
                AccountUser.account_id == self.id).delete()
            db.session.commit()
            return True
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot revoke user privileges on account {0}. DETAIL: {1}'.
                format(self.name, e))
            return False

    def update(self):
        """
        Fetch accounts from PowerDNS and syncs them into DB
        """
        db_accounts = Account.query.all()
        list_db_accounts = [d.name for d in db_accounts]
        current_app.logger.info("Found {} accounts in PowerDNS-Admin".format(
            len(list_db_accounts)))
        headers = {'X-API-Key': self.PDNS_API_KEY}
        try:
            jdata = utils.fetch_json(
                urljoin(self.PDNS_STATS_URL,
                        self.API_EXTENDED_URL + '/servers/localhost/zones'),
                headers=headers,
                timeout=int(Setting().get('pdns_api_timeout')),
                verify=Setting().get('verify_ssl_connections'))
            list_jaccount = set(d['account'] for d in jdata if d['account'])
            current_app.logger.info("Found {} accounts in PowerDNS".format(
                len(list_jaccount)))

            try:
                # Remove accounts that don't exist any more
                should_removed_db_account = list(
                    set(list_db_accounts).difference(list_jaccount))
                for account_name in should_removed_db_account:
                    account_id = self.get_id_by_name(account_name)
                    if not account_id:
                        continue
                    current_app.logger.info("Deleting account for {0}".format(account_name))
                    account = Account.query.get(account_id)
                    account.delete_account(commit=False)
            except Exception as e:
                current_app.logger.error(
                    'Can not delete account from DB. DETAIL: {0}'.format(e))
                current_app.logger.debug(traceback.format_exc())

            for account_name in list_jaccount:
                account_id = self.get_id_by_name(account_name)
                if account_id:
                    continue
                current_app.logger.info("Creating account for {0}".format(account_name))
                account = Account(name=account_name)
                db.session.add(account)

            db.session.commit()
            current_app.logger.info('Update accounts finished')
            return {
                'status': 'ok',
                'msg': 'Account table has been updated successfully'
            }
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(
                'Cannot update account table. Error: {0}'.format(e))
            return {'status': 'error', 'msg': 'Cannot update account table'}
