import email as email_lib
import email.header
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from datetime import datetime, timezone

from flask import request, jsonify, current_app

from app.api import api_bp
from app.api.auth import token_required, admin_required
from app.extensions import db
from app.models.email_account import EmailAccount
from app.models.user import User
from app.services.crypto_service import encrypt_field, decrypt_field


def _get_account_or_err(account_id, user):
    account = db.session.get(EmailAccount, account_id)
    if not account or account.user_id != user.id:
        return None, (jsonify({'error': 'Konto nicht gefunden'}), 404)
    return account, None


def _get_imap_connection(account, user_password_key):
    import imapclient
    password = decrypt_field(account.password_encrypted, user_password_key)
    host = account.imap_host
    port = account.imap_port

    if account.imap_ssl:
        conn = imapclient.IMAPClient(host, port=port, ssl=True)
    else:
        conn = imapclient.IMAPClient(host, port=port, ssl=False)
        conn.starttls()

    conn.login(account.username, password)
    return conn


def _decode_header(header_value):
    if not header_value:
        return ''
    decoded_parts = email.header.decode_header(header_value)
    result = []
    for part, charset in decoded_parts:
        if isinstance(part, bytes):
            result.append(part.decode(charset or 'utf-8', errors='replace'))
        else:
            result.append(part)
    return ' '.join(result)


# --- Accounts ---

@api_bp.route('/email/accounts', methods=['GET'])
@token_required
def list_email_accounts():
    user = request.current_user
    accounts = EmailAccount.query.filter_by(user_id=user.id)\
        .order_by(EmailAccount.sort_order).all()
    return jsonify([a.to_dict() for a in accounts]), 200


@api_bp.route('/email/accounts', methods=['POST'])
@token_required
def create_email_account():
    user = request.current_user
    data = request.get_json()

    required = ['display_name', 'email_address', 'imap_host', 'smtp_host', 'username', 'password']
    for field in required:
        if not data.get(field):
            return jsonify({'error': f'{field} erforderlich'}), 400

    # Get encryption key from header
    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich (X-Encryption-Key Header)'}), 400

    encrypted_pw = encrypt_field(data['password'], enc_key)

    account = EmailAccount(
        user_id=user.id,
        display_name=data['display_name'],
        email_address=data['email_address'],
        imap_host=data['imap_host'],
        imap_port=data.get('imap_port', 993),
        imap_ssl=data.get('imap_ssl', True),
        smtp_host=data['smtp_host'],
        smtp_port=data.get('smtp_port', 587),
        smtp_ssl=data.get('smtp_ssl', True),
        username=data['username'],
        password_encrypted=encrypted_pw,
        is_default=data.get('is_default', False),
        sort_order=data.get('sort_order', 0),
    )
    db.session.add(account)

    # Update email account count
    db.session.commit()

    return jsonify(account.to_dict()), 201


@api_bp.route('/email/accounts/<int:account_id>', methods=['PUT'])
@token_required
def update_email_account(account_id):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    data = request.get_json()
    for field in ['display_name', 'email_address', 'imap_host', 'imap_port',
                  'imap_ssl', 'smtp_host', 'smtp_port', 'smtp_ssl',
                  'username', 'is_default', 'sort_order']:
        if field in data:
            setattr(account, field, data[field])

    if 'password' in data and data['password']:
        enc_key = request.headers.get('X-Encryption-Key', '')
        if enc_key:
            account.password_encrypted = encrypt_field(data['password'], enc_key)

    db.session.commit()
    return jsonify(account.to_dict()), 200


@api_bp.route('/email/accounts/<int:account_id>', methods=['DELETE'])
@token_required
def delete_email_account(account_id):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    db.session.delete(account)
    db.session.commit()
    return jsonify({'message': 'E-Mail-Konto geloescht'}), 200


@api_bp.route('/email/accounts/<int:account_id>/test', methods=['POST'])
@token_required
def test_email_account(account_id):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich'}), 400

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.logout()
        return jsonify({'message': 'Verbindung erfolgreich'}), 200
    except Exception as e:
        return jsonify({'error': f'Verbindungsfehler: {str(e)}'}), 400


# --- Folders ---

@api_bp.route('/email/accounts/<int:account_id>/folders', methods=['GET'])
@token_required
def list_email_folders(account_id):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich'}), 400

    try:
        conn = _get_imap_connection(account, enc_key)
        folders_raw = conn.list_folders()
        folders = []
        for flags, delimiter, name in folders_raw:
            flag_strs = [f.decode() if isinstance(f, bytes) else f for f in flags]
            folders.append({
                'name': name,
                'delimiter': delimiter.decode() if isinstance(delimiter, bytes) else delimiter,
                'flags': flag_strs,
            })
        conn.logout()
        return jsonify(folders), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


# --- Messages ---

@api_bp.route('/email/accounts/<int:account_id>/folders/<path:folder>/messages', methods=['GET'])
@token_required
def list_messages(account_id, folder):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich'}), 400

    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 50, type=int)

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.select_folder(folder)

        # Get all UIDs, sorted newest first
        uids = conn.search(['ALL'])
        uids.reverse()

        total = len(uids)
        start = (page - 1) * limit
        page_uids = uids[start:start + limit]

        messages = []
        if page_uids:
            fetch_data = conn.fetch(page_uids, ['ENVELOPE', 'FLAGS', 'RFC822.SIZE'])
            for uid in page_uids:
                if uid not in fetch_data:
                    continue
                msg_data = fetch_data[uid]
                envelope = msg_data.get(b'ENVELOPE')
                flags = msg_data.get(b'FLAGS', ())
                size = msg_data.get(b'RFC822.SIZE', 0)

                flag_strs = [f.decode() if isinstance(f, bytes) else str(f) for f in flags]

                from_addr = ''
                if envelope and envelope.from_:
                    f = envelope.from_[0]
                    name = f.name.decode(errors='replace') if f.name else ''
                    mailbox = f.mailbox.decode(errors='replace') if f.mailbox else ''
                    host = f.host.decode(errors='replace') if f.host else ''
                    from_addr = f'{name} <{mailbox}@{host}>' if name else f'{mailbox}@{host}'

                subject = ''
                if envelope and envelope.subject:
                    subject = _decode_header(envelope.subject.decode(errors='replace'))

                date_str = ''
                if envelope and envelope.date:
                    date_str = envelope.date.isoformat() if hasattr(envelope.date, 'isoformat') else str(envelope.date)

                messages.append({
                    'uid': uid,
                    'subject': subject,
                    'from': from_addr,
                    'date': date_str,
                    'flags': flag_strs,
                    'size': size,
                    'seen': '\\Seen' in flag_strs,
                })

        conn.logout()
        return jsonify({
            'messages': messages,
            'total': total,
            'page': page,
            'limit': limit,
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@api_bp.route('/email/accounts/<int:account_id>/messages/<int:uid>', methods=['GET'])
@token_required
def get_message(account_id, uid):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    folder = request.args.get('folder', 'INBOX')

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.select_folder(folder)

        fetch_data = conn.fetch([uid], ['RFC822', 'FLAGS'])
        if uid not in fetch_data:
            conn.logout()
            return jsonify({'error': 'Nachricht nicht gefunden'}), 404

        raw = fetch_data[uid][b'RFC822']
        flags = fetch_data[uid].get(b'FLAGS', ())

        # Mark as seen
        conn.set_flags([uid], ['\\Seen'])
        conn.logout()

        msg = email_lib.message_from_bytes(raw)

        # Extract body
        html_body = ''
        text_body = ''
        attachments = []

        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                disposition = str(part.get('Content-Disposition', ''))

                if 'attachment' in disposition:
                    filename = part.get_filename() or 'attachment'
                    attachments.append({
                        'filename': _decode_header(filename),
                        'content_type': content_type,
                        'size': len(part.get_payload(decode=True) or b''),
                    })
                elif content_type == 'text/html':
                    html_body = part.get_payload(decode=True).decode(errors='replace')
                elif content_type == 'text/plain':
                    text_body = part.get_payload(decode=True).decode(errors='replace')
        else:
            content_type = msg.get_content_type()
            payload = msg.get_payload(decode=True)
            if payload:
                if content_type == 'text/html':
                    html_body = payload.decode(errors='replace')
                else:
                    text_body = payload.decode(errors='replace')

        return jsonify({
            'uid': uid,
            'subject': _decode_header(msg.get('Subject', '')),
            'from': _decode_header(msg.get('From', '')),
            'to': _decode_header(msg.get('To', '')),
            'cc': _decode_header(msg.get('Cc', '')),
            'date': msg.get('Date', ''),
            'html_body': html_body,
            'text_body': text_body,
            'attachments': attachments,
            'flags': [f.decode() if isinstance(f, bytes) else str(f) for f in flags],
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


# --- Send ---

@api_bp.route('/email/send', methods=['POST'])
@token_required
def send_email():
    user = request.current_user
    data = request.get_json()

    account_id = data.get('account_id')
    if not account_id:
        return jsonify({'error': 'Konto-ID erforderlich'}), 400

    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich'}), 400

    to_addr = data.get('to', '')
    cc_addr = data.get('cc', '')
    subject = data.get('subject', '')
    body_html = data.get('body_html', '')
    body_text = data.get('body_text', '')

    if not to_addr:
        return jsonify({'error': 'Empfaenger erforderlich'}), 400

    password = decrypt_field(account.password_encrypted, enc_key)

    # Build message
    msg = MIMEMultipart('alternative')
    msg['From'] = f'{account.display_name} <{account.email_address}>'
    msg['To'] = to_addr
    if cc_addr:
        msg['Cc'] = cc_addr
    msg['Subject'] = subject
    msg['Date'] = email_lib.utils.formatdate(localtime=True)

    if body_text:
        msg.attach(MIMEText(body_text, 'plain', 'utf-8'))
    if body_html:
        msg.attach(MIMEText(body_html, 'html', 'utf-8'))

    try:
        if account.smtp_ssl and account.smtp_port == 465:
            server = smtplib.SMTP_SSL(account.smtp_host, account.smtp_port)
        else:
            server = smtplib.SMTP(account.smtp_host, account.smtp_port)
            server.starttls()

        server.login(account.username, password)

        recipients = [to_addr]
        if cc_addr:
            recipients.extend(cc_addr.split(','))

        server.sendmail(account.email_address, recipients, msg.as_string())
        server.quit()

        return jsonify({'message': 'E-Mail gesendet'}), 200
    except Exception as e:
        return jsonify({'error': f'Sendefehler: {str(e)}'}), 500


# --- Flag / Move / Delete ---

@api_bp.route('/email/accounts/<int:account_id>/messages/<int:uid>/flag', methods=['POST'])
@token_required
def flag_message(account_id, uid):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    data = request.get_json()
    folder = data.get('folder', 'INBOX')
    flag = data.get('flag', '\\Seen')
    add = data.get('add', True)

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.select_folder(folder)
        if add:
            conn.add_flags([uid], [flag])
        else:
            conn.remove_flags([uid], [flag])
        conn.logout()
        return jsonify({'message': 'Flag gesetzt'}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@api_bp.route('/email/accounts/<int:account_id>/messages/<int:uid>/move', methods=['POST'])
@token_required
def move_message(account_id, uid):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    data = request.get_json()
    folder = data.get('folder', 'INBOX')
    target = data.get('target')

    if not target:
        return jsonify({'error': 'Ziel-Ordner erforderlich'}), 400

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.select_folder(folder)
        conn.move([uid], target)
        conn.logout()
        return jsonify({'message': 'Nachricht verschoben'}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@api_bp.route('/email/accounts/<int:account_id>/messages/<int:uid>', methods=['DELETE'])
@token_required
def delete_message(account_id, uid):
    user = request.current_user
    account, err = _get_account_or_err(account_id, user)
    if err:
        return err

    enc_key = request.headers.get('X-Encryption-Key', '')
    folder = request.args.get('folder', 'INBOX')

    try:
        conn = _get_imap_connection(account, enc_key)
        conn.select_folder(folder)
        conn.delete_messages([uid])
        conn.expunge()
        conn.logout()
        return jsonify({'message': 'Nachricht geloescht'}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


# --- Admin: manage email accounts for any user ---

@api_bp.route('/admin/users/<int:user_id>/email-accounts', methods=['GET'])
@admin_required
def admin_list_email_accounts(user_id):
    user = db.session.get(User, user_id)
    if not user:
        return jsonify({'error': 'Benutzer nicht gefunden'}), 404
    accounts = EmailAccount.query.filter_by(user_id=user_id)\
        .order_by(EmailAccount.sort_order).all()
    return jsonify([a.to_dict() for a in accounts]), 200


@api_bp.route('/admin/users/<int:user_id>/email-accounts', methods=['POST'])
@admin_required
def admin_create_email_account(user_id):
    user = db.session.get(User, user_id)
    if not user:
        return jsonify({'error': 'Benutzer nicht gefunden'}), 404

    data = request.get_json()
    required = ['display_name', 'email_address', 'imap_host', 'smtp_host', 'username', 'password']
    for field in required:
        if not data.get(field):
            return jsonify({'error': f'{field} erforderlich'}), 400

    enc_key = request.headers.get('X-Encryption-Key', '')
    if not enc_key:
        return jsonify({'error': 'Verschluesselungs-Key erforderlich (X-Encryption-Key Header)'}), 400

    account = EmailAccount(
        user_id=user_id,
        display_name=data['display_name'],
        email_address=data['email_address'],
        imap_host=data['imap_host'],
        imap_port=data.get('imap_port', 993),
        imap_ssl=data.get('imap_ssl', True),
        smtp_host=data['smtp_host'],
        smtp_port=data.get('smtp_port', 587),
        smtp_ssl=data.get('smtp_ssl', True),
        username=data['username'],
        password_encrypted=encrypt_field(data['password'], enc_key),
        is_default=data.get('is_default', False),
        sort_order=data.get('sort_order', 0),
    )
    db.session.add(account)
    db.session.commit()
    return jsonify(account.to_dict()), 201


@api_bp.route('/admin/email-accounts/<int:account_id>', methods=['PUT'])
@admin_required
def admin_update_email_account(account_id):
    account = db.session.get(EmailAccount, account_id)
    if not account:
        return jsonify({'error': 'Konto nicht gefunden'}), 404

    data = request.get_json()
    for field in ['display_name', 'email_address', 'imap_host', 'imap_port',
                  'imap_ssl', 'smtp_host', 'smtp_port', 'smtp_ssl',
                  'username', 'is_default', 'sort_order']:
        if field in data:
            setattr(account, field, data[field])

    if 'password' in data and data['password']:
        enc_key = request.headers.get('X-Encryption-Key', '')
        if enc_key:
            account.password_encrypted = encrypt_field(data['password'], enc_key)

    db.session.commit()
    return jsonify(account.to_dict()), 200


@api_bp.route('/admin/email-accounts/<int:account_id>', methods=['DELETE'])
@admin_required
def admin_delete_email_account(account_id):
    account = db.session.get(EmailAccount, account_id)
    if not account:
        return jsonify({'error': 'Konto nicht gefunden'}), 404

    db.session.delete(account)
    db.session.commit()
    return jsonify({'message': 'E-Mail-Konto geloescht'}), 200
