from __future__ import absolute_import
import datetime
from flask_sqlalchemy.query import Query as BaseQuery
from sqlalchemy import and_, desc
from dostadmin import db, db_model
from utils.helpers.helpers import get_current_isttime, replace_chars
from dostadmin.mixins import TimestampMixin
from datetime import timedelta


class ExperienceQuery(BaseQuery):
    def get_by_id(self, exp_id):
        return self.filter(Experience.id == exp_id).first()

    def get_all_experience_for_phone(self, user_phone):
        return self.filter(Experience.phone == user_phone).all()

    def get_total_experience_by_user_phone(self, phone):
        return self.filter(Experience.phone.contains(phone[-10:])).count()

    def find_experience_with_phone_status(
        self, phone, status, experience_type=None, order="asc"
    ):
        if not isinstance(status, list):
            status = [status]

        if not experience_type:
            experience_type = [Experience.Type.PHONECAST, Experience.Type.SELFPACEDPC]
        if order == "desc":
            experience = (
                self.filter(
                    and_(
                        Experience.phone == replace_chars(phone),
                        Experience.status.in_(status),
                        Experience.type.in_(experience_type),
                    )
                )
                .order_by(desc(Experience.start_date))
                .first()
            )
        else:
            experience = self.filter(
                and_(
                    Experience.phone == replace_chars(phone),
                    Experience.status.in_(status),
                    Experience.type.in_(experience_type),
                )
            ).first()

        return experience

    def get_active_experiences(self, experience_type=None, page=1, per_page=30):
        if not experience_type:
            experience_type = [Experience.Type.PHONECAST, Experience.Type.SELFPACEDPC]
        return self.filter(
            and_(
                Experience.status == Experience.Status.ACTIVE,
                Experience.type.in_(experience_type),
            )
        ).paginate(page=page, per_page=per_page, error_out=False)

    def get_active_experiences_for_user_id(self, user_id, experience_type=None):
        if not experience_type:
            experience_type = [Experience.Type.PHONECAST, Experience.Type.SELFPACEDPC]
        return self.filter(
            and_(
                Experience.user_id == user_id,
                Experience.status == Experience.Status.ACTIVE,
                Experience.type.in_(experience_type),
            )
        ).all()

    # TODO: remove this and check all the uses of the function
    def find_experience_with_phone(self, phone, experience_type=None):
        if not experience_type:
            experience_type = [Experience.Type.PHONECAST, Experience.Type.SELFPACEDPC]

        experience_statuses = [Experience.Status.ACTIVE, Experience.Status.PENDING]
        experience = (
            self.filter(
                and_(
                    Experience.phone == replace_chars(phone),
                    Experience.type.in_(experience_type),
                    Experience.status.in_(experience_statuses),
                )
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        if experience:
            return experience

        experience_statuses = [Experience.Status.CHURNED]
        experience = (
            self.filter(
                and_(
                    Experience.phone == replace_chars(phone),
                    Experience.type.in_(experience_type),
                    Experience.status.in_(experience_statuses),
                )
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        if experience:
            churned_user = (
                db_model.ChurnedUsers.query.get_active_churned_user_by_experience_id(
                    experience.id
                )
            )
            if churned_user:
                return experience

        experience = (
            self.filter(
                and_(
                    Experience.phone == replace_chars(phone),
                    Experience.type.in_(experience_type),
                )
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        return experience

    def find_latest_experience_id_for_a_phone(self, phone, experience_type=None):
        if not experience_type:
            experience_type = [Experience.Type.PHONECAST, Experience.Type.SELFPACEDPC]

            return (
                self.filter(
                    and_(
                        Experience.phone == replace_chars(phone),
                        Experience.type.in_(experience_type),
                    )
                )
                .order_by(desc(Experience.id))
                .first()
            )

        return None

    def find_oldest_experience_with_phone(self, phone, experience_type="pc"):
        return (
            self.filter(
                and_(
                    Experience.phone == replace_chars(phone),
                    Experience.type == experience_type,
                )
            )
            .order_by(Experience.start_date)
            .first()
        )

    def find_all_experiences_for_wa_id(self, wa_id):
        return self.filter(
            and_(Experience.wa_id == wa_id, Experience.Type == Experience.Type.WHATSAPP)
        ).all()

    def get_active_wa_experience_by_wa_id(self, wa_id):
        return self.filter(
            and_(
                Experience.status == Experience.Status.ACTIVE,
                Experience.type == Experience.Type.WHATSAPP,
                Experience.wa_id == wa_id,
            )
        ).first()

    def find_all_experiences_for_phone_and_status(self, phone, status):
        if not isinstance(status, list):
            status = [status]

        experiences = self.filter(
            and_(
                Experience.phone == replace_chars(phone),
                Experience.status.in_(status),
            )
        ).all()

        return experiences

    def get_by_user_id(self, user_id):
        experience_statuses = [Experience.Status.ACTIVE, Experience.Status.PENDING]
        experience = (
            self.filter(
                and_(
                    Experience.user_id == user_id,
                    Experience.status.in_(experience_statuses),
                )
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        if experience:
            return experience

        experience_statuses = [Experience.Status.CHURNED]
        experience = (
            self.filter(
                and_(
                    Experience.user_id == user_id,
                    Experience.status.in_(experience_statuses),
                )
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        if experience:
            churned_user = (
                db_model.ChurnedUsers.query.get_active_churned_user_by_experience_id(
                    experience.id
                )
            )
            if churned_user:
                return experience

        experience = (
            self.filter(
                Experience.user_id == user_id,
            )
            .order_by(desc(Experience.start_date))
            .first()
        )

        return experience

    def find_experience_by_provider_number_and_created_date(
        self, provider_number, date
    ):

        experiences = self.filter(
            db.func.date(Experience.created_on) == date,
            db.func.right(Experience.provider_number, 10).in_(provider_number),
        ).all()
        return experiences

    # TODO: this would fail when a phonecast user messages us on wa
    def get_or_create_wa_experience(
        self, wa_id, name="fnu", phone="", provider_number="", partner_name=None
    ):
        from dostadmin.db_model import User

        experience = self.get_active_wa_experience_by_wa_id(wa_id)

        if experience:
            return experience

        experience = (
            self.filter(Experience.wa_id == wa_id)
            .order_by(Experience.start_date)
            .first()
        )

        if experience:
            return experience

        # check if phonecast experience exists
        experience = self.find_experience_with_phone(phone)
        if experience:
            # phonecast experience exists, create a new WA experience
            experience = Experience.create_new_experience(
                experience_data=experience,
                start_date=get_current_isttime().date(),
                provider_number=provider_number,
                status=Experience.Status.SIGNUP,
                wa_id=wa_id,
                experience_type=Experience.Type.WHATSAPP,
            )
            return experience

        user = User.add_new_user(name, partner_name=partner_name)
        experience = Experience.create_new_experience(
            {
                "user_id": user.id,
                "wa_id": wa_id,
                "phone": phone,
                "type": Experience.Type.WHATSAPP,
                "provider_number": provider_number,
                "start_date": get_current_isttime().date(),
                "status": Experience.Status.SIGNUP,
            }
        )
        return experience

    def get_active_experience_without_group_for_today(self):
        today = get_current_isttime().date()
        experiences = self.filter(
            Experience.start_date == today,
            Experience.experiment_group_name.is_(None),
            Experience.status.in_(
                [Experience.Status.ACTIVE, Experience.Status.PENDING]
            ),
        ).all()
        return experiences

    def get_timeslot_wise_active_user_count(self, timecategory_id):
        user_count = Experience.query.filter(
            Experience.timecategory_id == timecategory_id,
            Experience.status.in_(
                [Experience.Status.ACTIVE, Experience.Status.PENDING]
            ),
        ).count()
        return user_count

    def get_experience_for_experiment_id(self, experiment_id):
        return self.filter(Experience.experiment_id == experiment_id).all()


class CustomizedExperience:
    def __init__(self, data):
        self.exp_id = data.get("exp_id")
        self.user_id = data.get("user_id")
        self.program_id = data.get("program_id")
        self.language_id = data.get("language_id")
        self.start_date = data.get("start_date")
        self.time = data.get("time")
        self.prev_content_id = data.get("prev_content_id")
        self.prev_content_version_id = data.get("prev_content_version_id")
        self.prev_deploy_datetime = data.get("prev_deploy_datetime")
        self.timecategory_id = data.get("timecategory_id")
        self.previous_campaign_name = data.get("previous_campaign_name")
        self.phone = data.get("phone")
        self.provider_number = data.get("provider_number")
        self.status = data.get("status")
        self.prev_programseq_id = data.get("prev_programseq_id")
        self.experience_type = data.get("experience_type")
        self.prev_campaign_status = data.get("campaign_status")
        self.experiment_id = data.get("experiment_id")
        self.experiment_group_name = data.get("experiment_group_name")
        self.experience_created_on = data.get("experience_created_on")


class Experience(TimestampMixin, db.Model):
    query_class = ExperienceQuery

    class Status:
        ACTIVE = "active"
        COMPLETED = "completed"
        TERMINATED = "terminated"
        QUEUED = "queued"
        PAUSED = "paused"
        SIGNUP = "signup"
        SIGNUP_UNCONFIRMED = "signup_unconfirmed"
        SIGNUP_CONFIRMED = "signup_confirmed"
        RESIGNUP_CONFIRMED = "resignup_confirmed"
        DND_WAIT = "dnd_wait"
        DND_NOTCLEARED = "dnd_notcleared"
        MODIFIED = "modified"
        SIGNUP_REJECTED = "signup_rejected"
        UNSUBSCRIBE = "unsub"
        INVALID_WA = "invalid_wa"
        PENDING = "pending"
        CHURNED = "churned"

        CHOICE = (
            (ACTIVE, "active"),
            (COMPLETED, "completed"),
            (TERMINATED, "terminated"),
            (SIGNUP, "signup"),
            (SIGNUP_CONFIRMED, "signup_confirmed"),
            (SIGNUP_UNCONFIRMED, "signup_unconfirmed"),
            (QUEUED, "queued"),
            (PAUSED, "paused"),
            (MODIFIED, "modified"),
            (DND_WAIT, "dnd_wait"),
            (DND_NOTCLEARED, "dnd_notcleared"),
            (SIGNUP_REJECTED, "signup_rejected"),
            (UNSUBSCRIBE, "unsubscribe"),
            (INVALID_WA, "invalid_wa"),
            (PENDING, "pending"),
        )

    class Cohort:
        WEEKDAY = 0
        WEEKEND = 3

    class Type:
        PHONECAST = "pc"
        WHATSAPP = "wa"
        SELFPACEDPC = "selfpacedpc"

        CHOICE = ((SELFPACEDPC, "स्व निर्देशित फ़ोनकास्ट"),)

    __tablename__ = "experience"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    phone = db.Column(db.String(20), nullable=False)
    language_id = db.Column(db.Integer, db.ForeignKey("language.id"))
    timecategory_id = db.Column(db.Integer, db.ForeignKey("timecategory.id"))
    program_id = db.Column(db.Integer, db.ForeignKey("program.id"))
    start_date = db.Column(db.Date)
    end_date = db.Column(db.Date)
    status = db.Column(db.String(20), index=True)
    provider_number = db.Column(db.String(20))
    type = db.Column(db.String(20), index=True)
    experiment_id = db.Column(db.Integer, db.ForeignKey("experiment.id"), nullable=True)
    experiment_group_name = db.Column(db.String(100))
    wa_id = db.Column(db.String(20))

    missed_calls = db.relationship(
        "MissedCallLog",
        backref="experience",
        primaryjoin="Experience.id == MissedCallLog.experience_id",
    )
    time_category = db.relationship(
        "Timecategory",
        back_populates="experiences",
    )
    user = db.relationship(
        "User",
        back_populates="experiences",
    )

    def __repr__(self):
        return (
            "\n Experience: id_"
            + str(self.id)
            + " user_id"
            + str(self.user_id)
            + ", phone:"
            + self.phone
        )

    @classmethod
    def mark_experience_as_completed(cls, exp_id):
        exp = Experience.query.get_by_id(exp_id)
        exp.status = Experience.Status.COMPLETED
        db.session.commit()

    @classmethod
    def get_next_start_date(cls, today):
        return today + datetime.timedelta(days=-today.weekday() + 1, weeks=1)

    @classmethod
    def handle_callin_feedback(cls, experience, callin_value):
        # if we move them directly to active they would start getting calls
        # so we need to move them to signed up confirmed and later on to active
        if callin_value == 1:
            if experience.status == Experience.Status.SIGNUP:
                experience.status = Experience.Status.SIGNUP_CONFIRMED
                experience.start_date = Experience.get_next_start_date(
                    datetime.date.today()
                )
        db.session.commit()

    @classmethod
    def update_experience_status(cls, experience, status):
        experience.status = status
        db.session.commit()

    @classmethod
    def update_experience_program(cls, experience, program_id):
        experience.program_id = program_id
        db.session.commit()

    @classmethod
    def create_new_experience(
        cls,
        experience_data,
        new_program_id=None,
        start_date=None,
        status="",
        provider_number=None,
        wa_id=None,
        experience_type=None,
    ):
        from dostadmin.db_model import Language, Timecategory

        if isinstance(experience_data, Experience):
            experience = Experience(
                user_id=experience_data.user_id,
                phone=experience_data.phone,
                language_id=experience_data.language_id,
                timecategory_id=experience_data.timecategory_id,
                program_id=new_program_id,
                start_date=start_date
                if start_date
                else get_current_isttime().date() + datetime.timedelta(days=1),
                status=status if status else Experience.Status.SIGNUP_CONFIRMED,
                provider_number=provider_number
                if provider_number
                else experience_data.provider_number,
                wa_id=wa_id if wa_id else experience_data.wa_id,
                type=experience_type
                if experience_type
                else Experience.Type.SELFPACEDPC,
            )
        else:
            experience = Experience(
                user_id=experience_data.get("user_id"),
                phone=experience_data.get("phone"),
                language_id=experience_data.get(
                    "language_id", Language.query.find_language_id_with_name()
                ),
                timecategory_id=experience_data.get(
                    "timecategory_id",
                    Timecategory.query.find_timecategory_id_with_name(),
                ),
                start_date=experience_data.get(
                    "start_date", get_current_isttime().date()
                ),
                program_id=new_program_id,
                status=experience_data.get("status", Experience.Status.SIGNUP),
                provider_number=experience_data.get("provider_number"),
                type=experience_data.get("type"),
                wa_id=experience_data.get("wa_id"),
            )
        db.session.add(experience)
        db.session.flush()
        db.session.commit()
        return experience

    @classmethod
    def update_experience_time(cls, experience, timecategory_id):
        experience.timecategory_id = timecategory_id
        db.session.commit()

    @classmethod
    def update_experience_data_for_ended_experiments(cls, experience):
        experience.experiment_id = (None,)
        experience.experiment_group_name = (None,)

    @classmethod
    def update_experience_start_date(cls, experience, date):
        experience.start_date = date
        db.session.commit()

    @classmethod
    def update_experience_end_date(cls, experience, date):
        experience.end_date = date
        db.session.commit()

    @classmethod
    def delete_experience(cls, experience):
        try:
            db.session.delete(experience)
            db.session.commit()
        except Exception as e:
            return str(e)
