from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import JsonResponse, HttpResponse
from django.core.serializers.json import DjangoJSONEncoder
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.utils.html import escape, mark_safe
from django.utils.safestring import SafeString
import json
import xml.etree.ElementTree as ET
from .models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Checklistenfrage, VorgabeComment
from .utils import build_dokument_xml_element, prettify_xml
from abschnitte.utils import render_textabschnitte

from datetime import date
import parsedatetime

calendar=parsedatetime.Calendar()


def standard_list(request):
    dokumente = Dokument.objects.all()
    return render(request, 'standards/standard_list.html',
                  {'dokumente': dokumente}
                  )


def standard_detail(request, nummer,check_date=""):
    standard = get_object_or_404(Dokument, nummer=nummer)

    if check_date:
        check_date = calendar.parseDT(check_date)[0].date()
        standard.history = True
        standard.is_future = check_date > date.today()
    else:
        check_date = date.today()
        standard.history = False
        standard.is_future = False
    standard.check_date=check_date
    vorgaben = list(standard.vorgaben.order_by("thema","nummer").select_related("thema","dokument"))  # convert queryset to list so we can attach attributes

    standard.geltungsbereich_html = render_textabschnitte(standard.geltungsbereich_set.order_by("order").select_related("abschnitttyp"))
    standard.einleitung_html=render_textabschnitte(standard.einleitung_set.order_by("order"))
    for vorgabe in vorgaben:
        # Prepare Kurztext HTML
        vorgabe.kurztext_html = render_textabschnitte(vorgabe.vorgabekurztext_set.order_by("order").select_related("abschnitttyp","abschnitt"))
        vorgabe.langtext_html = render_textabschnitte(vorgabe.vorgabelangtext_set.order_by("order").select_related("abschnitttyp","abschnitt"))
        vorgabe.long_status=vorgabe.get_status(check_date,verbose=True)
        vorgabe.relevanzset=list(vorgabe.relevanz.all())

        referenz_items = []
        for r in vorgabe.referenzen.all():
            referenz_items.append(r.Path())
        vorgabe.referenzpfade = referenz_items
        
        # Add comment count
        if request.user.is_authenticated:
            if request.user.is_staff:
                vorgabe.comment_count = vorgabe.comments.count()
            else:
                vorgabe.comment_count = vorgabe.comments.filter(user=request.user).count()
        else:
            vorgabe.comment_count = 0

    return render(request, 'standards/standard_detail.html', {
        'standard': standard,
        'vorgaben': vorgaben,
    })


def standard_checkliste(request, nummer):
    standard = get_object_or_404(Dokument, nummer=nummer)
    vorgaben = list(standard.vorgaben.all())
    return render(request, 'standards/standard_checkliste.html', {
        'standard': standard,
        'vorgaben': vorgaben,
    })


def is_staff_user(user):
    return user.is_staff

@login_required
@user_passes_test(is_staff_user)
def incomplete_vorgaben(request):
    """
    Show table of all Vorgaben with completeness status:
    - References (✓ or ✗)
    - Stichworte (✓ or ✗)
    - Text (✓ or ✗)
    - Checklistenfragen (✓ or ✗)
    """
    # Get all active Vorgaben
    all_vorgaben = Vorgabe.objects.all().select_related('dokument', 'thema').prefetch_related(
        'referenzen', 'stichworte', 'checklistenfragen', 'vorgabekurztext_set', 'vorgabelangtext_set'
    )
    
    # Build table data
    vorgaben_data = []
    for vorgabe in all_vorgaben:
        has_references = vorgabe.referenzen.exists()
        has_stichworte = vorgabe.stichworte.exists()
        has_kurztext = vorgabe.vorgabekurztext_set.exists()
        has_langtext = vorgabe.vorgabelangtext_set.exists()
        has_text = has_kurztext or has_langtext
        has_checklistenfragen = vorgabe.checklistenfragen.exists()
        
        # Only include Vorgaben that are incomplete in at least one way
        if not (has_references and has_stichworte and has_text and has_checklistenfragen):
            vorgaben_data.append({
                'vorgabe': vorgabe,
                'has_references': has_references,
                'has_stichworte': has_stichworte,
                'has_text': has_text,
                'has_checklistenfragen': has_checklistenfragen,
                'is_complete': has_references and has_stichworte and has_text and has_checklistenfragen
            })
    
    # Sort by document number and Vorgabe number
    vorgaben_data.sort(key=lambda x: (x['vorgabe'].dokument.nummer, x['vorgabe'].Vorgabennummer()))
    
    return render(request, 'standards/incomplete_vorgaben.html', {
        'vorgaben_data': vorgaben_data,
    })


def standard_json(request, nummer):
    """
    Export a single Dokument as JSON
    """
    # Get the document with all related data
    dokument = get_object_or_404(
        Dokument.objects.prefetch_related(
            'autoren', 'pruefende', 'vorgaben__thema', 
            'vorgaben__referenzen', 'vorgaben__stichworte',
            'vorgaben__checklistenfragen', 'vorgaben__vorgabekurztext_set',
            'vorgaben__vorgabelangtext_set', 'geltungsbereich_set',
            'einleitung_set', 'changelog__autoren'
        ),
        nummer=nummer
    )

    # Build document structure (reusing logic from export_json command)
    doc_data = {
        "Typ": dokument.dokumententyp.name if dokument.dokumententyp else "",
        "Nummer": dokument.nummer,
        "Name": dokument.name,
        "Autoren": [autor.name for autor in dokument.autoren.all()],
        "Pruefende": [pruefender.name for pruefender in dokument.pruefende.all()],
        "Gueltigkeit": {
            "Von": dokument.gueltigkeit_von.strftime("%Y-%m-%d") if dokument.gueltigkeit_von else "",
            "Bis": dokument.gueltigkeit_bis.strftime("%Y-%m-%d") if dokument.gueltigkeit_bis else None
        },
        "SignaturCSO": dokument.signatur_cso,
        "Geltungsbereich": {},
        "Einleitung": {},
        "Ziel": "",
        "Grundlagen": "",
        "Changelog": [],
        "Anhänge": dokument.anhaenge,
        "Verantwortlich": "Information Security Management BIT",
        "Klassifizierung": None,
        "Glossar": {},
        "Vorgaben": []
    }

    # Process Geltungsbereich sections
    geltungsbereich_sections = []
    for gb in dokument.geltungsbereich_set.all().order_by('order'):
        geltungsbereich_sections.append({
            "typ": gb.abschnitttyp.abschnitttyp if gb.abschnitttyp else "text",
            "inhalt": gb.inhalt
        })
    
    if geltungsbereich_sections:
        doc_data["Geltungsbereich"] = {
            "Abschnitt": geltungsbereich_sections
        }

    # Process Einleitung sections
    einleitung_sections = []
    for ei in dokument.einleitung_set.all().order_by('order'):
        einleitung_sections.append({
            "typ": ei.abschnitttyp.abschnitttyp if ei.abschnitttyp else "text",
            "inhalt": ei.inhalt
        })
    
    if einleitung_sections:
        doc_data["Einleitung"] = {
            "Abschnitt": einleitung_sections
        }

    # Process Changelog entries
    changelog_entries = []
    for cl in dokument.changelog.all().order_by('-datum'):
        changelog_entries.append({
            "Datum": cl.datum.strftime("%Y-%m-%d"),
            "Autoren": [autor.name for autor in cl.autoren.all()],
            "Aenderung": cl.aenderung
        })
    
    doc_data["Changelog"] = changelog_entries

    # Process Vorgaben for this document
    vorgaben = dokument.vorgaben.all().order_by('order')
    
    for vorgabe in vorgaben:
        # Get Kurztext and Langtext sections
        kurztext_sections = []
        for kt in vorgabe.vorgabekurztext_set.all().order_by('order'):
            kurztext_sections.append({
                "typ": kt.abschnitttyp.abschnitttyp if kt.abschnitttyp else "text",
                "inhalt": kt.inhalt
            })
        
        langtext_sections = []
        for lt in vorgabe.vorgabelangtext_set.all().order_by('order'):
            langtext_sections.append({
                "typ": lt.abschnitttyp.abschnitttyp if lt.abschnitttyp else "text", 
                "inhalt": lt.inhalt
            })

        # Build text structures following Langtext pattern
        kurztext = {
            "Abschnitt": kurztext_sections if kurztext_sections else []
        } if kurztext_sections else {}
        langtext = {
            "Abschnitt": langtext_sections if langtext_sections else []
        } if langtext_sections else {}

        # Get references and keywords
        referenzen = [f"{ref.name_nummer}: {ref.name_text}" if ref.name_text else ref.name_nummer for ref in vorgabe.referenzen.all()]
        stichworte = [stw.stichwort for stw in vorgabe.stichworte.all()]
        
        # Get checklist questions
        checklistenfragen = [cf.frage for cf in vorgabe.checklistenfragen.all()]

        vorgabe_data = {
            "Nummer": str(vorgabe.nummer),
            "Titel": vorgabe.titel,
            "Thema": vorgabe.thema.name if vorgabe.thema else "",
            "Kurztext": kurztext,
            "Langtext": langtext,
            "Referenz": referenzen,
            "Gueltigkeit": {
                "Von": vorgabe.gueltigkeit_von.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_von else "",
                "Bis": vorgabe.gueltigkeit_bis.strftime("%Y-%m-%d") if vorgabe.gueltigkeit_bis else None
            },
            "Checklistenfragen": checklistenfragen,
            "Stichworte": stichworte
        }
        
        doc_data["Vorgaben"].append(vorgabe_data)

    # Return JSON response
    return JsonResponse(doc_data, json_dumps_params={'indent': 2, 'ensure_ascii': False}, encoder=DjangoJSONEncoder)


def standard_xml(request, nummer):
    """
    Export a single Dokument as XML
    """
    # Get the document with all related data
    dokument = get_object_or_404(
        Dokument.objects.prefetch_related(
            'autoren', 'pruefende', 'vorgaben__thema',
            'vorgaben__referenzen', 'vorgaben__stichworte',
            'vorgaben__checklistenfragen', 'vorgaben__vorgabekurztext_set',
            'vorgaben__vorgabelangtext_set', 'geltungsbereich_set',
            'einleitung_set', 'changelog__autoren'
        ),
        nummer=nummer
    )

    # Create a temporary root element to build the document
    root = ET.Element('root')
    build_dokument_xml_element(dokument, root)

    # Get the actual document element (first child of root)
    doc_element = root[0]

    xml_str = ET.tostring(doc_element, encoding='unicode', method='xml')
    xml_output = prettify_xml(xml_str)

    response = HttpResponse(xml_output, content_type='application/xml; charset=utf-8')
    response['Content-Disposition'] = f'attachment; filename="{dokument.nummer}.xml"'
    return response


@login_required
def get_vorgabe_comments(request, vorgabe_id):
    """Get comments for a specific Vorgabe"""
    vorgabe = get_object_or_404(Vorgabe, id=vorgabe_id)
    
    if request.user.is_staff:
        # Staff can see all comments
        comments = vorgabe.comments.all().select_related('user').order_by('created_at')
    else:
        # Regular users can only see their own comments
        comments = vorgabe.comments.filter(user=request.user).select_related('user').order_by('created_at')
    
    comments_data = []
    for comment in comments:
        # Escape HTML but preserve line breaks
        escaped_text = escape(comment.text).replace('\n', '<br>')
        comments_data.append({
            'id': comment.id,
            'text': escaped_text,
            'user': escape(comment.user.first_name+" "+comment.user.last_name),
            'created_at': comment.created_at.strftime('%d.%m.%Y %H:%M'),
            'updated_at': comment.updated_at.strftime('%d.%m.%Y %H:%M'),
            'is_own': comment.user == request.user
        })
    
    response = JsonResponse({'comments': comments_data})
    response['Content-Security-Policy'] = "default-src 'self'"
    response['X-Content-Type-Options'] = 'nosniff'
    return response


@require_POST
@login_required
def add_vorgabe_comment(request, vorgabe_id):
    """Add a new comment to a Vorgabe"""
    vorgabe = get_object_or_404(Vorgabe, id=vorgabe_id)
    
    try:
        data = json.loads(request.body)
        text = data.get('text', '').strip()
        
        # Validate input
        if not text:
            return JsonResponse({'error': 'Kommentar darf nicht leer sein'}, status=400)
        
        if len(text) > 2000:  # Reasonable length limit
            return JsonResponse({'error': 'Kommentar ist zu lang (max 2000 Zeichen)'}, status=400)
        
        # Additional XSS prevention - check for dangerous patterns
        dangerous_patterns = ['<script', 'javascript:', 'onload=', 'onerror=', 'onclick=', 'onmouseover=']
        text_lower = text.lower()
        for pattern in dangerous_patterns:
            if pattern in text_lower:
                return JsonResponse({'error': 'Kommentar enthält ungültige Zeichen'}, status=400)
        
        comment = VorgabeComment.objects.create(
            vorgabe=vorgabe,
            user=request.user,
            text=text
        )
        
        # Escape HTML but preserve line breaks
        escaped_text = escape(comment.text).replace('\n', '<br>')
        response = JsonResponse({
            'success': True,
            'comment': {
                'id': comment.id,
                'text': escaped_text,
                'user': escape(comment.user.username),
                'created_at': comment.created_at.strftime('%d.%m.%Y %H:%M'),
                'updated_at': comment.updated_at.strftime('%d.%m.%Y %H:%M'),
                'is_own': True
            }
        })
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response
        
    except json.JSONDecodeError:
        response = JsonResponse({'error': 'Ungültige Daten'}, status=400)
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response
    except Exception as e:
        response = JsonResponse({'error': 'Serverfehler'}, status=500)
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response


@require_POST
@login_required
def delete_vorgabe_comment(request, comment_id):
    """Delete a comment (only own comments or staff can delete)"""
    comment = get_object_or_404(VorgabeComment, id=comment_id)
    
    # Check if user can delete this comment
    if comment.user != request.user and not request.user.is_staff:
        response = JsonResponse({'error': 'Keine Berechtigung zum Löschen dieses Kommentars'}, status=403)
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response
    
    try:
        comment.delete()
        response = JsonResponse({'success': True})
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response
    except Exception as e:
        response = JsonResponse({'error': 'Serverfehler'}, status=500)
        response['Content-Security-Policy'] = "default-src 'self'"
        response['X-Content-Type-Options'] = 'nosniff'
        return response


@login_required
def user_comments(request):
    """
    Display all comments made by the logged-in user, grouped by document.
    """
    # Get all comments by the current user
    user_comments = VorgabeComment.objects.filter(
        user=request.user
    ).select_related('vorgabe', 'vorgabe__dokument').order_by(
        'vorgabe__dokument__nummer', '-created_at'
    )
    
    # Group comments by document
    comments_by_document = {}
    for comment in user_comments:
        dokument = comment.vorgabe.dokument
        if dokument not in comments_by_document:
            comments_by_document[dokument] = []
        comments_by_document[dokument].append(comment)
    
    return render(request, 'standards/user_comments.html', {
        'comments_by_document': comments_by_document,
        'total_comments': user_comments.count(),
    })


@login_required
@user_passes_test(is_staff_user)
def all_comments(request):
    """
    Display all comments from all users, grouped by document.
    Staff only.
    """
    # Get all comments
    all_comments_qs = VorgabeComment.objects.select_related(
        'vorgabe', 'vorgabe__dokument', 'user'
    ).order_by(
        'vorgabe__dokument__nummer', '-created_at'
    )
    
    # Group comments by document
    comments_by_document = {}
    for comment in all_comments_qs:
        dokument = comment.vorgabe.dokument
        if dokument not in comments_by_document:
            comments_by_document[dokument] = []
        comments_by_document[dokument].append(comment)
    
    return render(request, 'standards/all_comments.html', {
        'comments_by_document': comments_by_document,
        'total_comments': all_comments_qs.count(),
    })
