# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Familiar Email Server Skill

Self-hosted email with:
- SMTP server (receive mail)
- IMAP server (client access)
- Encrypted local storage
- DKIM signing
- Spam filtering
- Relay fallback for sending
- DNS setup wizard
"""

import asyncio  # noqa: F401
import json  # noqa: F401
import logging
from dataclasses import dataclass, field
from datetime import datetime  # noqa: F401
from pathlib import Path
from typing import Any, Dict, List, Optional

from .dkim import DKIMManager
from .dns_manager import DNSManager
from .imap_server import IMAPServer
from .smtp_client import SMTPClient
from .smtp_server import SMTPServer
from .spam_filter import SpamFilter
from .storage import Mailbox, MailStorage, StoredMessage  # noqa: F401

logger = logging.getLogger(__name__)


@dataclass
class EmailServerConfig:
    """Email server configuration."""

    domain: str
    hostname: str = ""

    # Storage
    storage_path: str = "~/.familiar/mail"
    encryption_enabled: bool = True

    # Ports
    smtp_port: int = 25
    smtps_port: int = 465
    submission_port: int = 587
    imap_port: int = 993

    # TLS
    tls_auto: bool = True
    tls_cert_path: Optional[str] = None
    tls_key_path: Optional[str] = None

    # Relay (for better deliverability)
    relay_enabled: bool = True
    relay_host: Optional[str] = None
    relay_port: int = 587
    relay_username: Optional[str] = None
    relay_password: Optional[str] = None

    # Spam filtering
    spam_enabled: bool = True
    spam_threshold: float = 5.0

    # Users
    users: List[Dict[str, Any]] = field(default_factory=list)

    def __post_init__(self):
        if not self.hostname:
            self.hostname = f"mail.{self.domain}"


class EmailServerSkill:
    """
    Self-hosted email server skill for Familiar.

    Provides complete email infrastructure:
    - Receive mail via SMTP
    - Send mail via direct SMTP or relay
    - IMAP access for standard email clients
    - Encrypted storage at rest
    - DKIM signing for authentication
    - Basic spam filtering
    - DNS configuration assistance
    """

    name = "email_server"
    description = "Self-hosted email server with encryption"

    def __init__(self, agent, config: Dict[str, Any]):
        self.agent = agent
        self.config = EmailServerConfig(**config.get("email_server", {}))

        # Initialize storage path
        self.storage_path = Path(self.config.storage_path).expanduser()
        self.storage_path.mkdir(parents=True, exist_ok=True)

        # Components (initialized in start())
        self.storage: Optional[MailStorage] = None
        self.smtp_server: Optional[SMTPServer] = None
        self.imap_server: Optional[IMAPServer] = None
        self.smtp_client: Optional[SMTPClient] = None
        self.dns_manager: Optional[DNSManager] = None
        self.dkim_manager: Optional[DKIMManager] = None
        self.spam_filter: Optional[SpamFilter] = None

        self._running = False

    async def start(self):
        """Start all email server components."""
        logger.info(f"Starting email server for {self.config.domain}")

        # Initialize encryption key
        encryption_key = await self._get_encryption_key()

        # Initialize storage
        self.storage = MailStorage(
            self.storage_path, encryption_key if self.config.encryption_enabled else None
        )
        await self.storage.initialize()

        # Initialize DKIM
        self.dkim_manager = DKIMManager(self.config.domain, self.storage_path / "dkim")
        await self.dkim_manager.initialize()

        # Initialize DNS manager
        self.dns_manager = DNSManager(self.config.domain, self.config.hostname, self.dkim_manager)

        # Initialize spam filter
        if self.config.spam_enabled:
            self.spam_filter = SpamFilter(threshold=self.config.spam_threshold)

        # Initialize SMTP client (for sending)
        self.smtp_client = SMTPClient(self.config, self.dkim_manager)

        # Initialize SMTP server (for receiving)
        self.smtp_server = SMTPServer(
            self.config, self.storage, self.spam_filter, self._on_message_received
        )

        # Initialize IMAP server (for client access)
        self.imap_server = IMAPServer(self.config, self.storage)

        # Create default users
        for user_config in self.config.users:
            await self.storage.create_mailbox(
                user_config["address"], user_config.get("password_hash")
            )

        # Start servers
        await self.smtp_server.start()
        await self.imap_server.start()

        self._running = True
        logger.info(f"Email server started for {self.config.domain}")

    async def stop(self):
        """Stop all email server components."""
        self._running = False

        if self.smtp_server:
            await self.smtp_server.stop()

        if self.imap_server:
            await self.imap_server.stop()

        logger.info("Email server stopped")

    async def _get_encryption_key(self) -> bytes:
        """Get or generate encryption key."""
        key_file = self.storage_path / ".encryption_key"

        if key_file.exists():
            return key_file.read_bytes()

        # Generate new key
        from cryptography.fernet import Fernet

        key = Fernet.generate_key()

        key_file.write_bytes(key)
        key_file.chmod(0o600)

        return key

    async def _on_message_received(self, message: StoredMessage):
        """Handle newly received message."""
        # Notify Familiar agent
        if self.agent:
            await self.agent.notify(
                f"📧 New email from {message.sender}: {message.subject}",
                data={
                    "type": "email_received",
                    "message_id": message.id,
                    "sender": message.sender,
                    "subject": message.subject,
                },
            )

    # ═══════════════════════════════════════════════════════════════
    # Public API
    # ═══════════════════════════════════════════════════════════════

    async def send(
        self,
        to: str,
        subject: str,
        body: str,
        from_address: Optional[str] = None,
        html_body: Optional[str] = None,
        attachments: Optional[List[Dict]] = None,
        reply_to: Optional[str] = None,
        cc: Optional[List[str]] = None,
        bcc: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """
        Send an email.

        Args:
            to: Recipient email address
            subject: Email subject
            body: Plain text body
            from_address: Sender address (must be valid for domain)
            html_body: Optional HTML body
            attachments: List of {'filename': str, 'content': bytes, 'mime_type': str}
            reply_to: Reply-to address
            cc: CC recipients
            bcc: BCC recipients

        Returns:
            {'success': bool, 'message_id': str, 'error': str}
        """
        if not from_address:
            from_address = f"familiar@{self.config.domain}"

        try:
            result = await self.smtp_client.send(
                from_address=from_address,
                to=to,
                subject=subject,
                body=body,
                html_body=html_body,
                attachments=attachments,
                reply_to=reply_to,
                cc=cc,
                bcc=bcc,
            )

            return {
                "success": True,
                "message_id": result["message_id"],
            }

        except Exception as e:
            logger.error(f"Failed to send email: {e}")
            return {
                "success": False,
                "error": str(e),
            }

    async def get_mailbox(
        self,
        address: str,
        folder: str = "INBOX",
        limit: int = 50,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """
        Get messages from a mailbox.

        Args:
            address: Email address
            folder: Folder name (INBOX, Sent, Drafts, Trash, Spam)
            limit: Maximum messages to return
            offset: Offset for pagination

        Returns:
            List of message summaries
        """
        mailbox = await self.storage.get_mailbox(address)
        if not mailbox:
            return []

        messages = await mailbox.get_messages(folder, limit, offset)

        return [
            {
                "id": msg.id,
                "sender": msg.sender,
                "subject": msg.subject,
                "date": msg.date.isoformat(),
                "read": msg.read,
                "flagged": msg.flagged,
                "has_attachments": len(msg.attachments) > 0,
                "preview": msg.body[:200] if msg.body else "",
            }
            for msg in messages
        ]

    async def get_message(
        self,
        address: str,
        message_id: str,
    ) -> Optional[Dict[str, Any]]:
        """
        Get a specific message with full content.

        Args:
            address: Email address
            message_id: Message ID

        Returns:
            Full message data or None
        """
        mailbox = await self.storage.get_mailbox(address)
        if not mailbox:
            return None

        message = await mailbox.get_message(message_id)
        if not message:
            return None

        # Mark as read
        await mailbox.mark_read(message_id)

        return {
            "id": message.id,
            "sender": message.sender,
            "to": message.to,
            "cc": message.cc,
            "subject": message.subject,
            "date": message.date.isoformat(),
            "body": message.body,
            "html_body": message.html_body,
            "attachments": [
                {
                    "filename": att["filename"],
                    "mime_type": att["mime_type"],
                    "size": len(att["content"]),
                }
                for att in message.attachments
            ],
            "headers": message.headers,
        }

    async def move_message(
        self,
        address: str,
        message_id: str,
        to_folder: str,
    ) -> bool:
        """Move a message to a different folder."""
        mailbox = await self.storage.get_mailbox(address)
        if not mailbox:
            return False

        return await mailbox.move_message(message_id, to_folder)

    async def delete_message(
        self,
        address: str,
        message_id: str,
        permanent: bool = False,
    ) -> bool:
        """Delete a message (move to Trash or permanently delete)."""
        mailbox = await self.storage.get_mailbox(address)
        if not mailbox:
            return False

        if permanent:
            return await mailbox.delete_message(message_id)
        else:
            return await mailbox.move_message(message_id, "Trash")

    async def search(
        self,
        address: str,
        query: str,
        folder: Optional[str] = None,
        limit: int = 50,
    ) -> List[Dict[str, Any]]:
        """
        Search messages.

        Args:
            address: Email address
            query: Search query
            folder: Optional folder to search (None = all)
            limit: Maximum results

        Returns:
            List of matching message summaries
        """
        mailbox = await self.storage.get_mailbox(address)
        if not mailbox:
            return []

        messages = await mailbox.search(query, folder, limit)

        return [
            {
                "id": msg.id,
                "folder": msg.folder,
                "sender": msg.sender,
                "subject": msg.subject,
                "date": msg.date.isoformat(),
                "preview": msg.body[:200] if msg.body else "",
            }
            for msg in messages
        ]

    # ═══════════════════════════════════════════════════════════════
    # DNS & Setup
    # ═══════════════════════════════════════════════════════════════

    async def get_dns_records(self) -> Dict[str, Any]:
        """
        Get required DNS records for the domain.

        Returns configuration needed at DNS provider.
        """
        return await self.dns_manager.get_required_records()

    async def verify_dns(self) -> Dict[str, Any]:
        """
        Verify DNS records are properly configured.

        Returns status of each required record.
        """
        return await self.dns_manager.verify_records()

    async def get_setup_guide(self) -> str:
        """Get human-readable DNS setup guide."""
        return await self.dns_manager.get_setup_guide()

    # ═══════════════════════════════════════════════════════════════
    # User Management
    # ═══════════════════════════════════════════════════════════════

    async def create_user(
        self,
        address: str,
        password: str,
    ) -> bool:
        """Create a new email user."""
        import hashlib

        password_hash = hashlib.sha256(password.encode()).hexdigest()

        return await self.storage.create_mailbox(address, password_hash)

    async def list_users(self) -> List[str]:
        """List all email users."""
        return await self.storage.list_mailboxes()

    async def get_stats(self) -> Dict[str, Any]:
        """Get email server statistics."""
        stats = {
            "domain": self.config.domain,
            "running": self._running,
            "users": await self.storage.list_mailboxes() if self.storage else [],
            "ports": {
                "smtp": self.config.smtp_port,
                "smtps": self.config.smtps_port,
                "imap": self.config.imap_port,
            },
            "relay_enabled": self.config.relay_enabled,
            "encryption_enabled": self.config.encryption_enabled,
            "spam_filter_enabled": self.config.spam_enabled,
        }

        if self.storage:
            stats["storage"] = await self.storage.get_stats()

        return stats


# Skill registration
def register(agent, config):
    """Register the email server skill."""
    return EmailServerSkill(agent, config)
