from django.contrib import admin, messages
from django.urls import reverse
from django.utils.html import format_html
from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import RangeDateTimeFilter
from unfold.decorators import display

from core.admin_ui import PortalAdminMixin
from .models import ImportBatch, ImportedObject, ImportRow
from .services import analyse_batch, execute_batch, refresh_batch_counts, rollback_batch, simulate_batch


@admin.action(description="1 — Analisar os ficheiros selecionados")
def analyse_selected(modeladmin, request, queryset):
    analysed = failed = 0
    for batch in queryset:
        if batch.status == ImportBatch.Status.IMPORTED:
            modeladmin.message_user(request, f"{batch}: um lote importado não pode ser novamente analisado.", messages.WARNING)
            continue
        try:
            analyse_batch(batch)
            analysed += 1
        except Exception as exc:
            failed += 1
            modeladmin.message_user(request, f"{batch}: {exc}", messages.ERROR)
    if analysed:
        modeladmin.message_user(request, f"{analysed} ficheiro(s) analisado(s). Reveja e classifique todas as linhas.", messages.SUCCESS)
    if failed:
        modeladmin.message_user(request, f"{failed} análise(s) falharam.", messages.WARNING)


@admin.action(description="2 — Validar seleção e marcar como pronta")
def mark_ready(modeladmin, request, queryset):
    ready = blocked = 0
    for batch in queryset:
        blocked_rows = batch.rows.filter(
            severity=ImportRow.Severity.ERROR,
            decision__in=(ImportRow.Decision.PENDING, ImportRow.Decision.IMPORT),
        ).count()
        pending_rows = batch.rows.filter(decision=ImportRow.Decision.PENDING).count()
        if not batch.rows.exists() or blocked_rows or pending_rows:
            blocked += 1
            continue
        batch.status = ImportBatch.Status.READY
        batch.simulation_summary = {}
        batch.simulated_at = None
        batch.error_message = ""
        batch.rows.update(processing_status=ImportRow.ProcessingStatus.PENDING, processing_message="", processed_at=None)
        batch.save(update_fields=["status", "simulation_summary", "simulated_at", "error_message", "updated_at"])
        ready += 1
    if ready:
        modeladmin.message_user(request, f"{ready} importação(ões) ficaram prontas para simular.", messages.SUCCESS)
    if blocked:
        modeladmin.message_user(request, f"{blocked} importação(ões) têm erros, linhas por decidir ou ainda não foram analisadas.", messages.WARNING)


@admin.action(description="3 — Simular a importação (não cria dados)")
def simulate_selected(modeladmin, request, queryset):
    ok = failed = 0
    for batch in queryset:
        try:
            summary = simulate_batch(batch)
            if summary["failed"]:
                failed += 1
                modeladmin.message_user(request, f"{batch}: simulação com {summary['failed']} erro(s).", messages.ERROR)
            else:
                ok += 1
                modeladmin.message_user(
                    request,
                    f"{batch}: {summary['created']} linhas criarão dados, {summary['reused']} reutilizarão dados e {summary['skipped']} serão ignoradas.",
                    messages.SUCCESS,
                )
        except Exception as exc:
            failed += 1
            modeladmin.message_user(request, f"{batch}: {exc}", messages.ERROR)
    if ok:
        modeladmin.message_user(request, f"{ok} lote(s) simulados sem erros. Já podem ser confirmados.", messages.SUCCESS)


@admin.action(description="4 — CONFIRMAR e executar a importação simulada")
def execute_selected(modeladmin, request, queryset):
    imported = failed = 0
    for batch in queryset:
        try:
            summary = execute_batch(batch, request.user)
            imported += 1
            modeladmin.message_user(
                request,
                f"{batch.code}: importação concluída — {summary['objects_created']} registos criados e {summary['objects_reused']} reutilizados.",
                messages.SUCCESS,
            )
        except Exception as exc:
            failed += 1
            modeladmin.message_user(request, f"{batch}: {exc}", messages.ERROR)
    if failed:
        modeladmin.message_user(request, f"{failed} lote(s) não foram importados.", messages.WARNING)


@admin.action(description="Anular uma importação confirmada")
def rollback_selected(modeladmin, request, queryset):
    rolled_back = failed = 0
    for batch in queryset:
        try:
            summary = rollback_batch(batch)
            rolled_back += 1
            modeladmin.message_user(request, f"{batch.code}: {summary['deleted_objects']} registos criados foram removidos.", messages.SUCCESS)
        except Exception as exc:
            failed += 1
            modeladmin.message_user(request, f"{batch}: {exc}", messages.ERROR)
    if failed:
        modeladmin.message_user(request, "Algumas anulações foram bloqueadas para proteger dados alterados ou já utilizados.", messages.WARNING)


@admin.action(description="Reiniciar análise (apenas lotes não importados)")
def reset_analysis(modeladmin, request, queryset):
    count = blocked = 0
    for batch in queryset:
        if batch.status in {ImportBatch.Status.IMPORTED, ImportBatch.Status.ROLLED_BACK}:
            blocked += 1
            continue
        batch.rows.all().delete()
        batch.imported_objects.all().delete()
        batch.status = ImportBatch.Status.UPLOADED
        batch.total_rows = batch.valid_rows = batch.warning_rows = batch.error_rows = batch.ignored_rows = 0
        batch.analysed_at = batch.simulated_at = None
        batch.simulation_summary = {}
        batch.error_message = ""
        batch.save()
        count += 1
    if count:
        modeladmin.message_user(request, f"{count} análise(s) reiniciada(s).", messages.INFO)
    if blocked:
        modeladmin.message_user(request, f"{blocked} lote(s) importados/anulados foram preservados para auditoria.", messages.WARNING)


@admin.action(description="Marcar linhas como Importar")
def rows_mark_import(modeladmin, request, queryset):
    queryset.exclude(severity=ImportRow.Severity.ERROR).update(
        decision=ImportRow.Decision.IMPORT,
        processing_status=ImportRow.ProcessingStatus.PENDING,
        processing_message="",
        processed_at=None,
    )
    for batch in ImportBatch.objects.filter(rows__in=queryset).distinct():
        refresh_batch_counts(batch)


@admin.action(description="Marcar linhas como Ignorar")
def rows_mark_ignore(modeladmin, request, queryset):
    queryset.update(decision=ImportRow.Decision.IGNORE, processing_status=ImportRow.ProcessingStatus.PENDING)
    for batch in ImportBatch.objects.filter(rows__in=queryset).distinct():
        refresh_batch_counts(batch)


@admin.action(description="Arquivar como histórico")
def rows_mark_archive(modeladmin, request, queryset):
    queryset.update(decision=ImportRow.Decision.ARCHIVE, processing_status=ImportRow.ProcessingStatus.PENDING)
    for batch in ImportBatch.objects.filter(rows__in=queryset).distinct():
        refresh_batch_counts(batch)


@admin.action(description="Voltar a colocar por decidir")
def rows_mark_pending(modeladmin, request, queryset):
    queryset.update(decision=ImportRow.Decision.PENDING, processing_status=ImportRow.ProcessingStatus.PENDING)
    for batch in ImportBatch.objects.filter(rows__in=queryset).distinct():
        refresh_batch_counts(batch)


class ImportedObjectInline(TabularInline):
    model = ImportedObject
    extra = 0
    can_delete = False
    fields = ("label", "content_type", "object_id", "action", "created_at")
    readonly_fields = fields
    tab = True

    def has_add_permission(self, request, obj=None):
        return False


@admin.register(ImportBatch)
class ImportBatchAdmin(PortalAdminMixin, ModelAdmin):
    portal_icon = "upload_file"
    portal_kicker = "DADOS"
    portal_description = "Analise, reveja, simule e confirme a importação do Excel com histórico e possibilidade de anulação controlada."
    portal_tone = "violet"
    portal_stats = (
        {"label": "Ficheiros", "icon": "upload_file", "tone": "primary", "caption": "Histórico completo"},
        {"label": "Em revisão", "icon": "fact_check", "tone": "warning", "filters": {"status": ImportBatch.Status.REVIEW}, "caption": "Requer decisão"},
        {"label": "Simulados", "icon": "preview", "tone": "violet", "filters": {"status": ImportBatch.Status.SIMULATED}, "caption": "Prontos a confirmar"},
        {"label": "Importados", "icon": "database_upload", "tone": "success", "filters": {"status": ImportBatch.Status.IMPORTED}, "caption": "Dados criados"},
    )
    portal_related_links = (
        {"label": "Linhas de importação", "icon": "table_rows", "url_name": "admin:imports_importrow_changelist"},
        {"label": "Registos importados", "icon": "inventory", "url_name": "admin:imports_importedobject_changelist"},
        {"label": "Qualidade dos dados", "icon": "data_check", "url_name": "quality_center"},
    )

    list_display = ("batch_identity", "status_label", "rows_summary", "progress", "simulation_result", "imported_at", "created_by", "review_link")
    list_filter = ("status", ("created_at", RangeDateTimeFilter), ("analysed_at", RangeDateTimeFilter), ("imported_at", RangeDateTimeFilter))
    search_fields = ("code", "title", "original_filename", "notes", "error_message")
    readonly_fields = (
        "public_id", "code", "original_filename", "status", "total_rows", "valid_rows", "warning_rows", "error_rows",
        "ignored_rows", "analysed_at", "simulated_at", "imported_at", "rolled_back_at", "simulation_summary",
        "import_summary", "rollback_summary", "created_by", "executed_by", "error_message", "created_at", "updated_at",
    )
    actions = (analyse_selected, mark_ready, simulate_selected, execute_selected, rollback_selected, reset_analysis)
    inlines = (ImportedObjectInline,)
    list_fullwidth = True
    list_filter_submit = True

    fieldsets = (
        ("Ficheiro", {"fields": ("code", "title", "source_file", "selected_sheets"), "description": "Folhas suportadas: Alojamentos, Domínios, Websites e Domínios Associados."}),
        ("Análise e revisão", {"fields": ("status", "total_rows", "valid_rows", "warning_rows", "error_rows", "ignored_rows", "analysed_at", "error_message")}),
        ("Simulação", {"fields": ("simulated_at", "simulation_summary"), "description": "A simulação valida o plano sem criar qualquer dado real."}),
        ("Execução e anulação", {"fields": ("imported_at", "rolled_back_at", "import_summary", "rollback_summary", "executed_by"), "description": "A importação final cria os dados e regista todos os objetos para auditoria e anulação controlada."}),
        ("Notas", {"fields": ("notes",)}),
        ("Sistema", {"fields": ("public_id", "original_filename", "created_by", "created_at", "updated_at"), "classes": ("collapse",)}),
    )

    @display(description="Importação", header=True)
    def batch_identity(self, obj):
        return obj.code, obj.title or obj.original_filename or "Sem designação"

    @display(description="Estado", label={
        ImportBatch.Status.UPLOADED: "info", ImportBatch.Status.ANALYSING: "primary", ImportBatch.Status.REVIEW: "warning",
        ImportBatch.Status.READY: "success", ImportBatch.Status.SIMULATED: "primary", ImportBatch.Status.IMPORTING: "primary",
        ImportBatch.Status.IMPORTED: "success", ImportBatch.Status.ROLLED_BACK: "info", ImportBatch.Status.FAILED: "danger",
        ImportBatch.Status.CANCELLED: "info",
    }, ordering="status")
    def status_label(self, obj):
        return obj.get_status_display()

    @display(description="Linhas")
    def rows_summary(self, obj):
        return f"{obj.total_rows} · {obj.warning_rows} avisos · {obj.error_rows} erros"

    @display(description="Revisão")
    def progress(self, obj):
        return f"{obj.progress_percentage}%"

    @display(description="Simulação")
    def simulation_result(self, obj):
        if not obj.simulation_summary:
            return "—"
        return f"{obj.simulation_summary.get('created', 0)} criar · {obj.simulation_summary.get('reused', 0)} reutilizar"

    @display(description="Abrir")
    def review_link(self, obj):
        url = reverse("admin:imports_importrow_changelist") + f"?batch__id__exact={obj.pk}"
        return format_html('<a class="portal-inline-link" href="{}">Rever linhas →</a>', url)

    def save_model(self, request, obj, form, change):
        if obj.source_file and not obj.original_filename:
            obj.original_filename = obj.source_file.name.split("/")[-1]
        if not obj.title and obj.original_filename:
            obj.title = obj.original_filename.rsplit(".", 1)[0]
        if not obj.created_by_id:
            obj.created_by = request.user
        super().save_model(request, obj, form, change)


@admin.register(ImportRow)
class ImportRowAdmin(PortalAdminMixin, ModelAdmin):
    portal_icon = "table_rows"
    portal_kicker = "QUALIDADE DOS DADOS"
    portal_description = "Compare o conteúdo original com a proposta normalizada, corrija e decida o destino de cada linha."
    portal_tone = "cyan"
    portal_stats = (
        {"label": "Linhas analisadas", "icon": "table_rows", "tone": "primary", "caption": "Total da pré-visualização"},
        {"label": "Por decidir", "icon": "help", "tone": "warning", "filters": {"decision": ImportRow.Decision.PENDING}, "caption": "Requer revisão"},
        {"label": "Com erros", "icon": "error", "tone": "danger", "filters": {"severity": ImportRow.Severity.ERROR}, "caption": "Bloqueiam a importação"},
        {"label": "Processadas", "icon": "done_all", "tone": "success", "method": "count_processed", "caption": "Criadas ou associadas"},
    )
    portal_related_links = ({"label": "Ficheiros Excel", "icon": "upload_file", "url_name": "admin:imports_importbatch_changelist"},)

    list_display = ("row_identity", "record_type", "severity_label", "decision", "processing_label", "proposed_client_name", "proposed_domain_name", "proposed_renewal_date", "issues_short")
    list_filter = ("batch", "sheet_name", "record_type", "severity", "decision", "processing_status", "proposed_service_type")
    search_fields = ("source_label", "proposed_client_name", "proposed_service_name", "proposed_domain_name", "processing_message")
    list_editable = ("decision",)
    readonly_fields = (
        "public_id", "batch", "sheet_name", "row_number", "record_type", "source_label", "raw_data", "normalized_data",
        "severity", "issues", "duplicate_key", "processing_status", "processing_message", "processed_at", "created_at", "updated_at",
    )
    actions = (rows_mark_import, rows_mark_ignore, rows_mark_archive, rows_mark_pending)
    list_filter_submit = True
    list_fullwidth = True

    fieldsets = (
        ("Origem e validação", {"fields": ("batch", "sheet_name", "row_number", "record_type", "source_label", "severity", "issues", "decision")}),
        ("Proposta normalizada", {"fields": ("proposed_client_name", "proposed_service_name", "proposed_domain_name", "proposed_service_type", "proposed_renewal_date", "proposed_cost_price", "proposed_sale_price", "proposed_status")}),
        ("Associação a existente", {"fields": ("target_model", "target_object_id"), "description": "Use app.modelo e o ID quando a decisão for Associar a registo existente."}),
        ("Resultado", {"fields": ("processing_status", "processing_message", "processed_at")}),
        ("Notas de revisão", {"fields": ("proposed_notes",)}),
        ("Dados técnicos", {"fields": ("raw_data", "normalized_data", "duplicate_key")}),
        ("Sistema", {"fields": ("public_id", "created_at", "updated_at"), "classes": ("collapse",)}),
    )

    def count_processed(self, request, queryset):
        return queryset.filter(processing_status__in=(ImportRow.ProcessingStatus.CREATED, ImportRow.ProcessingStatus.LINKED)).count()

    @display(description="Origem", header=True)
    def row_identity(self, obj):
        return obj.source_label or obj.get_record_type_display(), f"{obj.sheet_name} · linha {obj.row_number}"

    @display(description="Validação", label={ImportRow.Severity.VALID: "success", ImportRow.Severity.WARNING: "warning", ImportRow.Severity.ERROR: "danger"}, ordering="severity")
    def severity_label(self, obj):
        return obj.get_severity_display()

    @display(description="Processamento", label={
        ImportRow.ProcessingStatus.PENDING: "info", ImportRow.ProcessingStatus.SIMULATED: "primary",
        ImportRow.ProcessingStatus.CREATED: "success", ImportRow.ProcessingStatus.LINKED: "success",
        ImportRow.ProcessingStatus.SKIPPED: "info", ImportRow.ProcessingStatus.FAILED: "danger",
        ImportRow.ProcessingStatus.ROLLED_BACK: "warning",
    }, ordering="processing_status")
    def processing_label(self, obj):
        return obj.get_processing_status_display()

    @display(description="Problemas")
    def issues_short(self, obj):
        text = obj.issues_text
        return text if len(text) <= 85 else f"{text[:82]}…"


@admin.register(ImportedObject)
class ImportedObjectAdmin(PortalAdminMixin, ModelAdmin):
    portal_icon = "inventory"
    portal_kicker = "AUDITORIA DE IMPORTAÇÃO"
    portal_description = "Rastreio dos registos criados, reutilizados ou associados por cada lote Excel."
    portal_tone = "green"
    list_display = ("label", "batch", "content_type", "object_id", "action_label", "created_at")
    list_filter = ("batch", "action", "content_type", ("created_at", RangeDateTimeFilter))
    search_fields = ("label", "batch__code", "batch__title")
    readonly_fields = ("public_id", "batch", "row", "content_type", "object_id", "action", "label", "object_updated_at", "rollback_blocked_reason", "created_at", "updated_at")
    list_fullwidth = True

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    @display(description="Ação", label={ImportedObject.Action.CREATED: "success", ImportedObject.Action.LINKED: "primary", ImportedObject.Action.REUSED: "info"}, ordering="action")
    def action_label(self, obj):
        return obj.get_action_display()
