"""
Test di integrazione IMAP con GreenMail (server IMAP mock via Docker).

Prerequisiti:
  - GreenMail in esecuzione: docker compose --profile greenmail up -d greenmail
  - Endpoint IMAP: localhost:3143 (plain) o localhost:3993 (SSL)
  - Credenziali test: test@example.com / secret

Esecuzione:
  cd worker && pytest tests/integration/test_imap_sync.py -v

I test sono contrassegnati con @pytest.mark.integration e saltati se
GREENMAIL_HOST non è raggiungibile.
"""

import asyncio
import os
import socket
import uuid
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

# ─── Skip automatico se GreenMail non disponibile ─────────────────────────────

GREENMAIL_HOST = os.getenv("GREENMAIL_HOST", "localhost")
GREENMAIL_IMAP_PORT = int(os.getenv("GREENMAIL_IMAP_PORT", "3143"))


def _is_greenmail_running() -> bool:
    """Verifica se GreenMail IMAP è raggiungibile."""
    try:
        with socket.create_connection((GREENMAIL_HOST, GREENMAIL_IMAP_PORT), timeout=2):
            return True
    except OSError:
        return False


skip_if_no_greenmail = pytest.mark.skipif(
    not _is_greenmail_running(),
    reason=f"GreenMail non disponibile su {GREENMAIL_HOST}:{GREENMAIL_IMAP_PORT}",
)

# ─── Fixture: mailbox mock ────────────────────────────────────────────────────


def make_mock_mailbox(
    email_address: str = "test@example.com",
    host: str = GREENMAIL_HOST,
    port: int = GREENMAIL_IMAP_PORT,
    user: str = "test@example.com",
    password: str = "secret",
    use_ssl: bool = False,
    last_sync_uid: int | None = None,
) -> MagicMock:
    """Crea un mock di Mailbox con credenziali cifrate per GreenMail."""
    from app.imap.connection import _decrypt
    from app.config import get_settings
    import base64
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    settings = get_settings()
    key = settings.encryption_key_bytes
    aesgcm = AESGCM(key)

    def encrypt(value: str) -> str:
        import os as _os
        nonce = _os.urandom(12)
        ct = aesgcm.encrypt(nonce, value.encode(), None)
        return base64.b64encode(nonce + ct).decode()

    mailbox = MagicMock()
    mailbox.id = uuid.uuid4()
    mailbox.tenant_id = uuid.uuid4()
    mailbox.email_address = email_address
    mailbox.status = "active"
    mailbox.last_sync_uid = last_sync_uid
    mailbox.sync_error_count = 0
    mailbox.sync_error_msg = None

    mailbox.imap_host_enc = encrypt(host)
    mailbox.imap_port_enc = encrypt(str(port))
    mailbox.imap_user_enc = encrypt(user)
    mailbox.imap_pass_enc = encrypt(password)
    mailbox.imap_use_ssl = use_ssl

    return mailbox


# ─── Test: connessione IMAP a GreenMail ──────────────────────────────────────

@skip_if_no_greenmail
@pytest.mark.asyncio
async def test_imap_connect_greenmail():
    """Verifica che IMAPConnection si connetta correttamente a GreenMail."""
    import aioimaplib
    from app.imap.connection import IMAPConnection

    mailbox = make_mock_mailbox()
    creds = IMAPConnection._decrypt_creds(mailbox)

    conn = IMAPConnection.__new__(IMAPConnection)
    client = await conn._connect(creds)

    assert client is not None

    # Verifica che SELECT INBOX abbia funzionato
    status, _ = await client.select("INBOX")
    assert status == "OK"

    await client.logout()


@skip_if_no_greenmail
@pytest.mark.asyncio
async def test_imap_search_empty_inbox():
    """SEARCH su inbox vuota restituisce lista vuota."""
    import aioimaplib
    from app.imap.connection import IMAPConnection

    mailbox = make_mock_mailbox(last_sync_uid=0)
    creds = IMAPConnection._decrypt_creds(mailbox)

    conn = IMAPConnection.__new__(IMAPConnection)
    client = await conn._connect(creds)

    # Cerca UID > 0 (tutti)
    status, data = await client.uid("SEARCH", "UID", "1:*")
    # Non deve sollevare eccezione
    assert status in ("OK", "NO")

    await client.logout()


# ─── Test: sync con DB mockato ────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_sync_new_messages_empty():
    """sync_new_messages con inbox vuota restituisce 0."""
    from app.imap.sync import sync_new_messages

    # Mock IMAP client che risponde con lista vuota
    mock_client = AsyncMock()
    mock_client.uid = AsyncMock(return_value=("OK", [b""]))

    # Mock mailbox
    mailbox = MagicMock()
    mailbox.id = uuid.uuid4()
    mailbox.tenant_id = uuid.uuid4()
    mailbox.email_address = "test@example.com"
    mailbox.last_sync_uid = 0

    # Mock DB
    mock_db = AsyncMock()
    mock_db.execute = AsyncMock(return_value=MagicMock(scalar_one_or_none=lambda: None))

    # Mock Redis
    mock_redis = AsyncMock()

    n = await sync_new_messages(mock_client, mailbox, mock_db, mock_redis)
    assert n == 0


@pytest.mark.asyncio
async def test_sync_skips_duplicate_uid():
    """sync_new_messages salta UID già presenti nel DB."""
    from app.imap.sync import sync_new_messages
    from sqlalchemy.engine import Result

    existing_uid = 42

    mock_client = AsyncMock()
    # SEARCH restituisce UID 42
    mock_client.uid = AsyncMock(return_value=("OK", [f"{existing_uid}".encode()]))

    mailbox = MagicMock()
    mailbox.id = uuid.uuid4()
    mailbox.tenant_id = uuid.uuid4()
    mailbox.email_address = "test@example.com"
    mailbox.last_sync_uid = 41

    # DB: UID 42 già presente
    mock_result = MagicMock()
    mock_result.scalar_one_or_none.return_value = uuid.uuid4()  # esiste già

    mock_db = AsyncMock()
    mock_db.execute = AsyncMock(return_value=mock_result)
    mock_db.flush = AsyncMock()
    mock_db.commit = AsyncMock()

    mock_redis = AsyncMock()

    n = await sync_new_messages(mock_client, mailbox, mock_db, mock_redis)
    # Deve restituire 0 perché il messaggio è già in DB
    assert n == 0


# ─── Test: parsing EML ────────────────────────────────────────────────────────

def test_parse_pec_message():
    """Parsing di un EML PEC tipico."""
    from app.imap.sync import _parse_eml

    raw = b"""From: mittente@aruba.pec.it
To: destinatario@pec.it
Subject: Comunicazione ufficiale n. 2026/001
Date: Wed, 18 Mar 2026 09:00:00 +0100
Message-ID: <20260318090000.1234@aruba.pec.it>
X-Ricevuta: posta-certificata
Content-Type: text/plain; charset=utf-8

Gentile destinatario,
in riferimento alla pratica n. 2026/001...
"""

    parsed = _parse_eml(raw)
    assert parsed["subject"] == "Comunicazione ufficiale n. 2026/001"
    assert parsed["from_address"] == "mittente@aruba.pec.it"
    assert parsed["pec_type"] == "posta_certificata"
    assert parsed["sent_at"] is not None
    assert "destinatario@pec.it" in parsed["to_addresses"]


def test_parse_ricevuta_avvenuta_consegna():
    """Parsing di una ricevuta di avvenuta consegna."""
    from app.imap.sync import _parse_eml

    raw = b"""From: posta-certificata@aruba.pec.it
To: mittente@aruba.pec.it
Subject: AVVENUTA CONSEGNA: Comunicazione n. 001
Date: Wed, 18 Mar 2026 09:05:00 +0100
Message-ID: <ricevuta.001@aruba.pec.it>
X-Riferimento-Message-ID: <20260318090000.1234@aruba.pec.it>
X-TipoRicevuta: avvenuta-consegna
Content-Type: text/plain; charset=utf-8

Il messaggio e' stato consegnato.
"""

    parsed = _parse_eml(raw)
    assert parsed["pec_type"] == "avvenuta_consegna"
    assert "AVVENUTA CONSEGNA" in parsed["subject"]


# ─── Test: backoff con connessione reale ─────────────────────────────────────

@skip_if_no_greenmail
@pytest.mark.asyncio
async def test_reconnect_after_logout():
    """Il sistema si riconnette dopo una disconnessione forzata."""
    from app.imap.connection import IMAPConnection

    mailbox = make_mock_mailbox()
    creds = IMAPConnection._decrypt_creds(mailbox)

    conn = IMAPConnection.__new__(IMAPConnection)

    # Prima connessione
    client1 = await conn._connect(creds)
    assert client1 is not None

    # Disconnessione forzata
    await client1.logout()

    # Seconda connessione (simula riconnessione)
    client2 = await conn._connect(creds)
    assert client2 is not None

    await client2.logout()
