import itertools
import logging
from datetime import date

from django.apps import apps
from django.conf import settings
from django.db import connection, transaction
from django.db.models import Q

from dimagi.utils.chunked import chunked

from corehq.apps.accounting.models import Subscription
from corehq.apps.accounting.utils import get_change_status
from corehq.apps.custom_data_fields.dbaccessors import get_by_domain_and_type
from corehq.apps.domain.utils import silence_during_tests
from corehq.apps.locations.views import LocationFieldsView
from corehq.apps.products.views import ProductFieldsView
from corehq.apps.userreports.dbaccessors import (
    delete_all_ucr_tables_for_domain,
)
from corehq.apps.users.views.mobile import UserFieldsView
from corehq.blobs import CODES, get_blob_db
from corehq.blobs.models import BlobMeta
from corehq.form_processor.backends.sql.dbaccessors import doc_type_to_state
from corehq.form_processor.interfaces.dbaccessors import (
    CaseAccessors,
    FormAccessors,
)
from corehq.util.log import with_progress_bar

logger = logging.getLogger(__name__)


class BaseDeletion(object):

    def __init__(self, app_label):
        self.app_label = app_label

    def is_app_installed(self):
        try:
            return bool(apps.get_app_config(self.app_label))
        except LookupError:
            return False


class CustomDeletion(BaseDeletion):

    def __init__(self, app_label, deletion_fn):
        super(CustomDeletion, self).__init__(app_label)
        self.deletion_fn = deletion_fn

    def execute(self, domain_name):
        if self.is_app_installed():
            self.deletion_fn(domain_name)


class RawDeletion(BaseDeletion):

    def __init__(self, app_label, raw_query):
        super(RawDeletion, self).__init__(app_label)
        self.raw_query = raw_query

    def execute(self, cursor, domain_name):
        if self.is_app_installed():
            cursor.execute(self.raw_query, [domain_name])


class ModelDeletion(BaseDeletion):

    def __init__(self, app_label, model_name, domain_filter_kwarg):
        super(ModelDeletion, self).__init__(app_label)
        self.domain_filter_kwarg = domain_filter_kwarg
        self.model_name = model_name

    def get_model_class(self):
        return apps.get_model(self.app_label, self.model_name)

    def execute(self, domain_name):
        if not domain_name:
            # The Django orm will properly turn a None domain_name to a
            # IS NULL filter. We don't want to allow deleting records for
            # NULL domain names since they might have special meaning (like
            # in some of the SMS models).
            raise RuntimeError("Expected a valid domain name")
        if self.is_app_installed():
            model = self.get_model_class()
            model.objects.filter(**{self.domain_filter_kwarg: domain_name}).delete()


def _delete_domain_backend_mappings(domain_name):
    model = apps.get_model('sms', 'SQLMobileBackendMapping')
    model.objects.filter(is_global=False, domain=domain_name).delete()


def _delete_domain_backends(domain_name):
    model = apps.get_model('sms', 'SQLMobileBackend')
    model.objects.filter(is_global=False, domain=domain_name).delete()


def _delete_web_user_membership(domain_name):
    from corehq.apps.users.models import WebUser
    active_web_users = WebUser.by_domain(domain_name)
    inactive_web_users = WebUser.by_domain(domain_name, is_active=False)
    for web_user in list(active_web_users) + list(inactive_web_users):
        web_user.delete_domain_membership(domain_name)
        if settings.UNIT_TESTING and not web_user.domain_memberships:
            web_user.delete()
        else:
            web_user.save()


def _terminate_subscriptions(domain_name):
    today = date.today()

    with transaction.atomic():
        current_subscription = Subscription.get_active_subscription_by_domain(domain_name)

        if current_subscription:
            current_subscription.date_end = today
            current_subscription.is_active = False
            current_subscription.save()

            current_subscription.transfer_credits()

            _, downgraded_privs, upgraded_privs = get_change_status(current_subscription.plan_version, None)
            current_subscription.subscriber.deactivate_subscription(
                downgraded_privileges=downgraded_privs,
                upgraded_privileges=upgraded_privs,
                old_subscription=current_subscription,
                new_subscription=None,
            )

        Subscription.visible_objects.filter(
            Q(date_start__gt=today) | Q(date_start=today, is_active=False),
            subscriber__domain=domain_name,
        ).update(is_hidden_to_ops=True)


def _delete_all_cases(domain_name):
    logger.info('Deleting cases...')
    case_accessor = CaseAccessors(domain_name)
    case_ids = case_accessor.get_case_ids_in_domain()
    for case_id_chunk in chunked(with_progress_bar(case_ids, stream=silence_during_tests()), 500):
        case_accessor.soft_delete_cases(list(case_id_chunk))
    logger.info('Deleting cases complete.')


def _delete_all_forms(domain_name):
    logger.info('Deleting forms...')
    form_accessor = FormAccessors(domain_name)
    form_ids = list(itertools.chain(*[
        form_accessor.get_all_form_ids_in_domain(doc_type=doc_type)
        for doc_type in doc_type_to_state
    ]))
    for form_id_chunk in chunked(with_progress_bar(form_ids, stream=silence_during_tests()), 500):
        form_accessor.soft_delete_forms(list(form_id_chunk))
    logger.info('Deleting forms complete.')


def _delete_data_files(domain_name):
    get_blob_db().bulk_delete(metas=list(BlobMeta.objects.partitioned_query(domain_name).filter(
        parent_id=domain_name,
        type_code=CODES.data_file,
    )))


def _delete_custom_data_fields(domain_name):
    # The CustomDataFieldsDefinition instances are cleaned up as part of the
    # bulk couch delete, but we also need to clear the cache
    logger.info('Deleting custom data fields...')
    for field_view in [LocationFieldsView, ProductFieldsView, UserFieldsView]:
        get_by_domain_and_type.clear(domain_name, field_view.field_type)
    logger.info('Deleting custom data fields complete.')


# We use raw queries instead of ORM because Django queryset delete needs to
# fetch objects into memory to send signals and handle cascades. It makes deletion very slow
# if we have a millions of rows in stock data tables.
DOMAIN_DELETE_OPERATIONS = [
    RawDeletion('stock', """
        DELETE FROM stock_stocktransaction
        WHERE report_id IN (SELECT id FROM stock_stockreport WHERE domain=%s)
    """),
    RawDeletion('stock', "DELETE FROM stock_stockreport WHERE domain=%s"),
    RawDeletion('stock', """
        DELETE FROM commtrack_stockstate
        WHERE product_id IN (SELECT product_id FROM products_sqlproduct WHERE domain=%s)
    """),
    ModelDeletion('products', 'SQLProduct', 'domain'),
    ModelDeletion('locations', 'SQLLocation', 'domain'),
    ModelDeletion('locations', 'LocationType', 'domain'),
    ModelDeletion('stock', 'DocDomainMapping', 'domain_name'),
    ModelDeletion('domain_migration_flags', 'DomainMigrationProgress', 'domain'),
    ModelDeletion('sms', 'DailyOutboundSMSLimitReached', 'domain'),
    ModelDeletion('sms', 'SMS', 'domain'),
    ModelDeletion('sms', 'SQLLastReadMessage', 'domain'),
    ModelDeletion('sms', 'ExpectedCallback', 'domain'),
    ModelDeletion('ivr', 'Call', 'domain'),
    ModelDeletion('sms', 'Keyword', 'domain'),
    ModelDeletion('sms', 'PhoneNumber', 'domain'),
    ModelDeletion('sms', 'MessagingSubEvent', 'parent__domain'),
    ModelDeletion('sms', 'MessagingEvent', 'domain'),
    ModelDeletion('sms', 'QueuedSMS', 'domain'),
    ModelDeletion('sms', 'SelfRegistrationInvitation', 'domain'),
    CustomDeletion('sms', _delete_domain_backend_mappings),
    ModelDeletion('sms', 'MobileBackendInvitation', 'domain'),
    CustomDeletion('sms', _delete_domain_backends),
    CustomDeletion('users', _delete_web_user_membership),
    CustomDeletion('accounting', _terminate_subscriptions),
    CustomDeletion('form_processor', _delete_all_cases),
    CustomDeletion('form_processor', _delete_all_forms),
    ModelDeletion('aggregate_ucrs', 'AggregateTableDefinition', 'domain'),
    ModelDeletion('app_manager', 'AppReleaseByLocation', 'domain'),
    ModelDeletion('app_manager', 'LatestEnabledBuildProfiles', 'domain'),
    ModelDeletion('app_manager', 'ResourceOverride', 'domain'),
    ModelDeletion('app_manager', 'GlobalAppConfig', 'domain'),
    ModelDeletion('case_importer', 'CaseUploadRecord', 'domain'),
    ModelDeletion('case_search', 'CaseSearchConfig', 'domain'),
    ModelDeletion('case_search', 'CaseSearchQueryAddition', 'domain'),
    ModelDeletion('case_search', 'FuzzyProperties', 'domain'),
    ModelDeletion('case_search', 'IgnorePatterns', 'domain'),
    ModelDeletion('cloudcare', 'ApplicationAccess', 'domain'),
    ModelDeletion('consumption', 'DefaultConsumption', 'domain'),
    ModelDeletion('data_analytics', 'GIRRow', 'domain_name'),
    ModelDeletion('data_analytics', 'MALTRow', 'domain_name'),
    ModelDeletion('data_dictionary', 'CaseType', 'domain'),
    ModelDeletion('data_interfaces', 'CaseRuleAction', 'rule__domain'),
    ModelDeletion('data_interfaces', 'CaseRuleCriteria', 'rule__domain'),
    ModelDeletion('data_interfaces', 'CaseRuleSubmission', 'rule__domain'),
    ModelDeletion('data_interfaces', 'CaseRuleSubmission', 'domain'),  # TODO
    ModelDeletion('data_interfaces', 'AutomaticUpdateRule', 'domain'),
    ModelDeletion('data_interfaces', 'DomainCaseRuleRun', 'domain'),
    ModelDeletion('domain', 'TransferDomainRequest', 'domain'),
    ModelDeletion('export', 'EmailExportWhenDoneRequest', 'domain'),
    CustomDeletion('export', _delete_data_files),
    ModelDeletion('locations', 'LocationFixtureConfiguration', 'domain'),
    ModelDeletion('ota', 'MobileRecoveryMeasure', 'domain'),
    ModelDeletion('ota', 'SerialIdBucket', 'domain'),
    ModelDeletion('phone', 'OwnershipCleanlinessFlag', 'domain'),
    ModelDeletion('phone', 'SyncLogSQL', 'domain'),
    ModelDeletion('registration', 'RegistrationRequest', 'domain'),
    ModelDeletion('reminders', 'EmailUsage', 'domain'),
    ModelDeletion('reports', 'ReportsSidebarOrdering', 'domain'),
    ModelDeletion('smsforms', 'SQLXFormsSession', 'domain'),
    ModelDeletion('translations', 'SMSTranslations', 'domain'),
    ModelDeletion('translations', 'TransifexBlacklist', 'domain'),
    ModelDeletion('userreports', 'AsyncIndicator', 'domain'),
    ModelDeletion('users', 'DomainRequest', 'domain'),
    ModelDeletion('users', 'Invitation', 'domain'),
    ModelDeletion('users', 'DomainPermissionsMirror', 'source'),
    ModelDeletion('zapier', 'ZapierSubscription', 'domain'),
    ModelDeletion('dhis2', 'Dhis2Connection', 'domain'),
    ModelDeletion('motech', 'RequestLog', 'domain'),
    ModelDeletion('couchforms', 'UnfinishedSubmissionStub', 'domain'),
    CustomDeletion('custom_data_fields', _delete_custom_data_fields),
    CustomDeletion('ucr', delete_all_ucr_tables_for_domain),
]


def apply_deletion_operations(domain_name):
    raw_ops, model_ops = _split_ops_by_type(DOMAIN_DELETE_OPERATIONS)

    with connection.cursor() as cursor:
        for op in raw_ops:
            op.execute(cursor, domain_name)

    for op in model_ops:
        op.execute(domain_name)


def _split_ops_by_type(ops):
    raw_ops = []
    model_ops = []
    for op in ops:
        if isinstance(op, RawDeletion):
            raw_ops.append(op)
        else:
            model_ops.append(op)
    return raw_ops, model_ops

[((31, 9, 31, 36), 'logging.getLogger', 'logging.getLogger', ({(31, 27, 31, 35): '__name__'}, {}), '(__name__)', False, 'import logging\n'), ((91, 12, 91, 60), 'django.apps.apps.get_model', 'apps.get_model', ({(91, 27, 91, 32): '"""sms"""', (91, 34, 91, 59): '"""SQLMobileBackendMapping"""'}, {}), "('sms', 'SQLMobileBackendMapping')", False, 'from django.apps import apps\n'), ((96, 12, 96, 53), 'django.apps.apps.get_model', 'apps.get_model', ({(96, 27, 96, 32): '"""sms"""', (96, 34, 96, 52): '"""SQLMobileBackend"""'}, {}), "('sms', 'SQLMobileBackend')", False, 'from django.apps import apps\n'), ((102, 23, 102, 53), 'corehq.apps.users.models.WebUser.by_domain', 'WebUser.by_domain', ({(102, 41, 102, 52): 'domain_name'}, {}), '(domain_name)', False, 'from corehq.apps.users.models import WebUser\n'), ((103, 25, 103, 72), 'corehq.apps.users.models.WebUser.by_domain', 'WebUser.by_domain', (), '', False, 'from corehq.apps.users.models import WebUser\n'), ((113, 12, 113, 24), 'datetime.date.today', 'date.today', ({}, {}), '()', False, 'from datetime import date\n'), ((141, 20, 141, 46), 'corehq.form_processor.interfaces.dbaccessors.CaseAccessors', 'CaseAccessors', ({(141, 34, 141, 45): 'domain_name'}, {}), '(domain_name)', False, 'from corehq.form_processor.interfaces.dbaccessors import CaseAccessors, FormAccessors\n'), ((150, 20, 150, 46), 'corehq.form_processor.interfaces.dbaccessors.FormAccessors', 'FormAccessors', ({(150, 34, 150, 45): 'domain_name'}, {}), '(domain_name)', False, 'from corehq.form_processor.interfaces.dbaccessors import CaseAccessors, FormAccessors\n'), ((76, 15, 76, 62), 'django.apps.apps.get_model', 'apps.get_model', ({(76, 30, 76, 44): 'self.app_label', (76, 46, 76, 61): 'self.model_name'}, {}), '(self.app_label, self.model_name)', False, 'from django.apps import apps\n'), ((115, 9, 115, 29), 'django.db.transaction.atomic', 'transaction.atomic', ({}, {}), '()', False, 'from django.db import connection, transaction\n'), ((116, 31, 116, 90), 'corehq.apps.accounting.models.Subscription.get_active_subscription_by_domain', 'Subscription.get_active_subscription_by_domain', ({(116, 78, 116, 89): 'domain_name'}, {}), '(domain_name)', False, 'from corehq.apps.accounting.models import Subscription\n'), ((172, 8, 172, 72), 'corehq.apps.custom_data_fields.dbaccessors.get_by_domain_and_type.clear', 'get_by_domain_and_type.clear', ({(172, 37, 172, 48): 'domain_name', (172, 50, 172, 71): 'field_view.field_type'}, {}), '(domain_name, field_view.field_type)', False, 'from corehq.apps.custom_data_fields.dbaccessors import get_by_domain_and_type\n'), ((263, 9, 263, 28), 'django.db.connection.cursor', 'connection.cursor', ({}, {}), '()', False, 'from django.db import connection, transaction\n'), ((125, 50, 125, 108), 'corehq.apps.accounting.utils.get_change_status', 'get_change_status', ({(125, 68, 125, 101): 'current_subscription.plan_version', (125, 103, 125, 107): 'None'}, {}), '(current_subscription.plan_version, None)', False, 'from corehq.apps.accounting.utils import get_change_status\n'), ((161, 4, 161, 17), 'corehq.blobs.get_blob_db', 'get_blob_db', ({}, {}), '()', False, 'from corehq.blobs import CODES, get_blob_db\n'), ((41, 24, 41, 59), 'django.apps.apps.get_app_config', 'apps.get_app_config', ({(41, 44, 41, 58): 'self.app_label'}, {}), '(self.app_label)', False, 'from django.apps import apps\n'), ((143, 68, 143, 90), 'corehq.apps.domain.utils.silence_during_tests', 'silence_during_tests', ({}, {}), '()', False, 'from corehq.apps.domain.utils import silence_during_tests\n'), ((155, 68, 155, 90), 'corehq.apps.domain.utils.silence_during_tests', 'silence_during_tests', ({}, {}), '()', False, 'from corehq.apps.domain.utils import silence_during_tests\n'), ((134, 12, 134, 35), 'django.db.models.Q', 'Q', (), '', False, 'from django.db.models import Q\n'), ((134, 38, 134, 74), 'django.db.models.Q', 'Q', (), '', False, 'from django.db.models import Q\n'), ((161, 41, 161, 88), 'corehq.blobs.models.BlobMeta.objects.partitioned_query', 'BlobMeta.objects.partitioned_query', ({(161, 76, 161, 87): 'domain_name'}, {}), '(domain_name)', False, 'from corehq.blobs.models import BlobMeta\n')]