"""Django forms for ECP Auth.

Provides forms for certificate upload during login and registration.
"""

from __future__ import annotations

import logging
from typing import Any

from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError

logger: logging.Logger = logging.getLogger(__name__)


class CertificateUploadForm(forms.Form):
    """Form for uploading a PKCS#12 certificate file for login.

    Fields:
        pkcs12_file: The PKCS#12 (.p12/.pfx) file containing the
            user's private key and certificate.
        pkcs12_password: Password to decrypt the PKCS#12 file.
    """

    pkcs12_file = forms.FileField(
        label="Certificate File (.p12 / .pfx)",
        help_text="Upload your PKCS#12 certificate file.",
        widget=forms.ClearableFileInput(
            attrs={
                "accept": ".p12,.pfx",
                "class": "form-control",
                "id": "id_pkcs12_file",
            }
        ),
    )

    pkcs12_password = forms.CharField(
        label="Certificate Password",
        widget=forms.PasswordInput(
            attrs={
                "class": "form-control",
                "placeholder": "Enter certificate password",
                "id": "id_pkcs12_password",
                "autocomplete": "off",
            }
        ),
        required=False,
        help_text="Password for the PKCS#12 file (leave blank if none).",
    )

    def clean_pkcs12_file(self) -> Any:
        """Validate the uploaded PKCS#12 file.

        Returns:
            The validated uploaded file.

        Raises:
            ValidationError: If the file is too large or has an invalid extension.
        """
        uploaded_file = self.cleaned_data.get("pkcs12_file")

        if uploaded_file is None:
            raise ValidationError("Please upload a certificate file.")

        # Check file size (max 5 MB)
        max_size: int = 5 * 1024 * 1024
        if uploaded_file.size > max_size:
            raise ValidationError(
                f"Certificate file is too large. "
                f"Maximum size: {max_size // (1024 * 1024)} MB."
            )

        # Check file extension
        name: str = uploaded_file.name.lower()
        if not name.endswith((".p12", ".pfx")):
            raise ValidationError(
                "Invalid file format. Please upload a .p12 or .pfx file."
            )

        return uploaded_file


class ECPRegistrationFormMixin:
    """Mixin to add ECP certificate generation fields to an existing registration form.

    Provides the `generate_ecp` and `ecp_password` fields.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.fields["generate_ecp"] = forms.BooleanField(
            label="Згенерувати ЕЦП ключ для швидкого входу",
            required=False,
            help_text="Система згенерує для вас унікальний сертифікат, який ви зможете використовувати замість пароля.",
            widget=forms.CheckboxInput(
                attrs={
                    "class": "h-4 w-4 rounded border-slate-300 text-emerald-600 focus:ring-emerald-500",
                }
            )
        )
        self.fields["ecp_password"] = forms.CharField(
            label="Пароль для нового ключа",
            required=False,
            help_text="Обов'язково, якщо ви обрали генерацію ключа.",
            widget=forms.PasswordInput(
                attrs={
                    "class": "w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none ring-emerald-400 transition focus:ring",
                    "placeholder": "Придумайте пароль до ключа",
                    "autocomplete": "new-password",
                }
            ),
        )

    def clean(self) -> dict[str, Any]:
        """Validate that ecp_password is provided if generate_ecp is True."""
        cleaned_data = super().clean()
        
        generate_ecp = cleaned_data.get("generate_ecp")
        ecp_password = cleaned_data.get("ecp_password")

        if generate_ecp and not ecp_password:
            self.add_error(
                "ecp_password",
                "Пароль обов'язковий при генерації ключа."
            )

        return cleaned_data


class ECPGenerateForm(forms.Form):
    """Form to generate a new ECP certificate for an existing user."""
    ecp_password = forms.CharField(
        label="Пароль для нового ключа",
        required=True,
        help_text="Цей пароль буде використано для шифрування файлу .p12",
        widget=forms.PasswordInput(
            attrs={
                "class": "w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none ring-emerald-400 transition focus:ring",
                "placeholder": "Придумайте надійний пароль",
                "autocomplete": "new-password",
            }
        ),
    )


class ECPLoginForm(AuthenticationForm):
    """Integrated AuthenticationForm that falls back to ECP if a file is provided."""

    pkcs12_file = forms.FileField(
        label="Файл з ключем (.p12 / .pfx)",
        required=False,
    )
    pkcs12_password = forms.CharField(
        label="Пароль ключа",
        required=False,
        widget=forms.PasswordInput(
            attrs={
                "class": "w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none ring-emerald-400 transition focus:ring",
                "placeholder": "Введіть пароль"
            }
        ),
    )

    def __init__(self, request: Any = None, *args: Any, **kwargs: Any) -> None:
        super().__init__(request, *args, **kwargs)
        self.fields["username"].required = False
        self.fields["password"].required = False

    def clean(self) -> dict[str, Any]:
        pkcs12_file = self.cleaned_data.get("pkcs12_file")
        pkcs12_password = self.cleaned_data.get("pkcs12_password", "")

        if pkcs12_file:
            from ecp_auth.parsers import parse_pkcs12
            from ecp_auth.validators import validate_certificate
            from ecp_auth.crypto import generate_challenge, sign_data
            
            try:
                pkcs12_data = pkcs12_file.read()
                private_key, certificate, _ = parse_pkcs12(pkcs12_data, pkcs12_password or None)
                
                cert_info = validate_certificate(certificate)
                
                from ecp_auth.models import UserCertificate
                if not UserCertificate.objects.filter(
                    fingerprint_sha256=cert_info.fingerprint_sha256,
                    is_active=True
                ).exists():
                    raise forms.ValidationError(
                        "Цей сертифікат не зареєстрований в системі.",
                        code="invalid_login"
                    )

                challenge = generate_challenge()
                signature_bytes = sign_data(private_key, challenge.nonce_bytes)
                import base64
                signature_b64 = base64.b64encode(signature_bytes).decode('utf-8')
                
                self.user_cache = authenticate(
                    self.request,
                    signature=signature_b64,
                    challenge=challenge,
                    certificate=certificate
                )

                if self.user_cache is None:
                    raise forms.ValidationError(
                        "Помилка перевірки електронного цифрового підпису.",
                        code="invalid_login"
                    )
                    
                self.request.ecp_login_params = {
                    "login_certificate_pem": cert_info.pem_data,
                    "login_fingerprint": cert_info.fingerprint_sha256,
                    "certificate_cn": cert_info.common_name,
                }
                
                return self.cleaned_data
            except forms.ValidationError:
                raise
            except Exception as exc:
                raise forms.ValidationError(
                    f"Помилка авторизації за сертифікатом: {str(exc)}",
                    code="invalid_login"
                )
                
        if not self.cleaned_data.get("username") or not self.cleaned_data.get("password"):
            raise forms.ValidationError(
                "Введіть логін і пароль, або завантажте файл ЕЦП.",
                code="invalid_login"
            )
            
        return super().clean()
