"""
Inbound Email Receiver Service

Processes incoming emails and creates ticket comments from replies.
Supports:
- IMAP polling for new emails
- Multiple email addresses (platform-wide and business-specific)
- Ticket ID extraction from reply-to addresses and subject lines
- Reply text extraction (stripping quoted content)
- User matching by email address
- Audit logging of all incoming emails

Usage:
    from tickets.email_receiver import TicketEmailReceiver
    from tickets.models import TicketEmailAddress

    email_address = TicketEmailAddress.objects.get(id=1)
    receiver = TicketEmailReceiver(email_address)
    processed_count = receiver.fetch_and_process_emails()
"""

import imaplib
import email
from email.header import decode_header
from email.utils import parseaddr, parsedate_to_datetime
import re
import logging
from typing import Optional, Tuple, List, Dict, Any
from datetime import datetime

from django.utils import timezone
from django.db import transaction

from .models import (
    Ticket,
    TicketComment,
    TicketEmailAddress,
    IncomingTicketEmail
)
from smoothschedule.identity.users.models import User

logger = logging.getLogger(__name__)


class TicketEmailReceiver:
    """
    Service for receiving and processing inbound ticket emails via IMAP.
    """

    # Patterns to extract ticket ID from email addresses
    # Matches: support+ticket-123@domain.com, ticket-123@domain.com, etc.
    TICKET_ID_PATTERNS = [
        r'ticket[_-](\d+)',  # ticket-123 or ticket_123
        r'\+ticket[_-](\d+)',  # +ticket-123 (subaddressing)
        r'reply[_-](\d+)',  # reply-123
        r'\[Ticket #(\d+)\]',  # [Ticket #123] in subject
        r'#(\d+)',  # #123 in subject (less specific)
    ]

    def __init__(self, email_address: TicketEmailAddress):
        """Initialize the email receiver with a specific email address configuration."""
        self.email_address = email_address
        self.connection = None

    def is_configured(self) -> bool:
        """Check if email receiving is properly configured."""
        return (self.email_address.is_imap_configured and
                self.email_address.is_smtp_configured and
                self.email_address.is_active)

    def connect(self) -> bool:
        """
        Establish connection to IMAP server.

        Returns:
            True if connection successful, False otherwise
        """
        if not self.email_address.is_imap_configured:
            logger.error(f"[{self.email_address.display_name}] IMAP settings not configured")
            return False

        try:
            if self.email_address.imap_use_ssl:
                self.connection = imaplib.IMAP4_SSL(
                    self.email_address.imap_host,
                    self.email_address.imap_port
                )
            else:
                self.connection = imaplib.IMAP4(
                    self.email_address.imap_host,
                    self.email_address.imap_port
                )

            self.connection.login(
                self.email_address.imap_username,
                self.email_address.imap_password
            )

            logger.info(f"[{self.email_address.display_name}] Connected to IMAP server {self.email_address.imap_host}")
            return True

        except imaplib.IMAP4.error as e:
            logger.error(f"[{self.email_address.display_name}] IMAP login failed: {e}")
            self._update_email_address_error(f"IMAP login failed: {e}")
            return False
        except Exception as e:
            logger.error(f"[{self.email_address.display_name}] Failed to connect to IMAP server: {e}")
            self._update_email_address_error(f"Connection failed: {e}")
            return False

    def disconnect(self):
        """Close IMAP connection."""
        if self.connection:
            try:
                self.connection.logout()
            except Exception:
                pass
            self.connection = None

    def fetch_and_process_emails(self) -> int:
        """
        Fetch new emails from IMAP and process them.

        Returns:
            Number of emails successfully processed
        """
        if not self.is_configured():
            logger.info("Email receiving not configured or disabled")
            return 0

        if not self.connect():
            return 0

        processed_count = 0

        try:
            # Select the inbox folder
            self.connection.select(self.email_address.imap_folder)

            # Search for unread emails
            status, messages = self.connection.search(None, 'UNSEEN')

            if status != 'OK':
                logger.error(f"[{self.email_address.display_name}] Failed to search emails: {status}")
                return 0

            email_ids = messages[0].split()
            logger.info(f"[{self.email_address.display_name}] Found {len(email_ids)} unread emails")

            for email_id in email_ids:
                try:
                    if self._process_single_email(email_id):
                        processed_count += 1
                except Exception as e:
                    logger.error(f"[{self.email_address.display_name}] Error processing email {email_id}: {e}")

            # Update email address with last check time
            self.email_address.last_check_at = timezone.now()
            self.email_address.last_error = ''
            self.email_address.emails_processed_count += processed_count
            self.email_address.save()

        except Exception as e:
            logger.error(f"[{self.email_address.display_name}] Error fetching emails: {e}")
            self._update_email_address_error(str(e))
        finally:
            self.disconnect()

        return processed_count

    def _process_single_email(self, email_id: bytes) -> bool:
        """
        Process a single email message.

        Args:
            email_id: IMAP email ID

        Returns:
            True if email was successfully processed
        """
        # Fetch the email
        status, msg_data = self.connection.fetch(email_id, '(RFC822)')

        if status != 'OK':
            logger.error(f"Failed to fetch email {email_id}")
            return False

        # Parse the email
        raw_email = msg_data[0][1]
        msg = email.message_from_bytes(raw_email)

        # Extract email data
        email_data = self._extract_email_data(msg)

        # Block emails sent to noreply@smoothschedule.com - just delete them
        if email_data['to_address'] == 'noreply@smoothschedule.com':
            logger.info(f"Deleting email sent to noreply@smoothschedule.com from {email_data['from_address']}")
            self._delete_email(email_id)
            return False

        # Check for duplicate (by message ID)
        if IncomingTicketEmail.objects.filter(message_id=email_data['message_id']).exists():
            logger.info(f"Duplicate email: {email_data['message_id']}")
            return False

        # Create incoming email record
        incoming_email = IncomingTicketEmail.objects.create(
            message_id=email_data['message_id'],
            from_address=email_data['from_address'],
            from_name=email_data['from_name'],
            to_address=email_data['to_address'],
            subject=email_data['subject'],
            body_text=email_data['body_text'],
            body_html=email_data['body_html'],
            extracted_reply=email_data['extracted_reply'],
            raw_headers=email_data['headers'],
            email_date=email_data['date'],
            ticket_id_from_email=email_data.get('ticket_id', ''),
        )

        # Try to match to a ticket
        ticket = self._find_matching_ticket(email_data)

        # Find the user by email address
        user = self._find_user_by_email(email_data['from_address'])

        if not ticket:
            # No matching ticket - create a new one as unassigned, low priority
            logger.info(f"No matching ticket for email from {email_data['from_address']}, creating new ticket")
            return self._create_new_ticket_from_email(email_data, incoming_email, user)

        if not user:
            # Check if sender matches ticket creator or assignee
            if ticket.creator and ticket.creator.email.lower() == email_data['from_address'].lower():
                user = ticket.creator
            elif ticket.assignee and ticket.assignee.email.lower() == email_data['from_address'].lower():
                user = ticket.assignee

        # Check if sender matches the ticket's external email (for tickets from non-registered users)
        is_external_sender = False
        if not user and ticket.external_email:
            if ticket.external_email.lower() == email_data['from_address'].lower():
                is_external_sender = True
                logger.info(f"Matched external sender {email_data['from_address']} to ticket #{ticket.id}")

        if not user and not is_external_sender:
            logger.warning(f"Could not match user for email from {email_data['from_address']}")
            incoming_email.mark_failed("Could not match sender to a user or external email")
            return False

        # Create the ticket comment
        try:
            with transaction.atomic():
                comment = TicketComment.objects.create(
                    ticket=ticket,
                    author=user,  # Will be None for external senders
                    comment_text=email_data['extracted_reply'] or email_data['body_text'],
                    is_internal=False,
                    source=TicketComment.Source.EMAIL,
                    incoming_email=incoming_email,
                    # Store external sender info if no user
                    external_author_email=email_data['from_address'] if is_external_sender else None,
                    external_author_name=email_data['from_name'] if is_external_sender else '',
                )

                # Update ticket status if it was awaiting response
                if ticket.status == Ticket.Status.AWAITING_RESPONSE:
                    # If customer/external sender replied, set to open
                    if user == ticket.creator or is_external_sender:
                        ticket.status = Ticket.Status.OPEN
                        ticket.save()

                incoming_email.mark_processed(ticket=ticket, user=user)

                logger.info(f"Created comment on ticket #{ticket.id} from email")
                return True

        except Exception as e:
            logger.error(f"Failed to create comment: {e}")
            incoming_email.mark_failed(str(e))
            return False

    def _create_new_ticket_from_email(
        self,
        email_data: Dict[str, Any],
        incoming_email: 'IncomingTicketEmail',
        user: Optional[User]
    ) -> bool:
        """
        Create a new ticket from an incoming email that doesn't match existing tickets.

        Args:
            email_data: Extracted email data
            incoming_email: The IncomingTicketEmail record
            user: The matched user (if found)

        Returns:
            True if ticket was created successfully
        """
        try:
            with transaction.atomic():
                # Extract subject - use as ticket subject
                subject = email_data['subject'] or 'Email Support Request'
                # Remove common prefixes like "Re:", "Fwd:", etc.
                import re
                subject = re.sub(r'^(Re|Fwd|FW|RE|FWD):\s*', '', subject, flags=re.IGNORECASE).strip()
                if not subject:
                    subject = 'Email Support Request'

                # Get the email body for description
                description = email_data['body_text'] or email_data['extracted_reply'] or ''
                if not description and email_data['body_html']:
                    description = self._html_to_text(email_data['body_html'])

                # Create the ticket - unassigned, low priority, platform type
                ticket = Ticket.objects.create(
                    tenant=None,  # Platform-level ticket
                    creator=user,  # May be None if sender not in system
                    assignee=None,  # Unassigned
                    ticket_type=Ticket.TicketType.PLATFORM,
                    status=Ticket.Status.OPEN,
                    priority=Ticket.Priority.LOW,
                    category=Ticket.Category.GENERAL_INQUIRY,
                    subject=subject[:255],  # Truncate to field max length
                    description=description,
                    is_sandbox=False,
                    # Store external sender info if not a registered user
                    external_email=email_data['from_address'] if not user else None,
                    external_name=email_data['from_name'] if not user else '',
                )

                # Create an initial comment with the email content
                TicketComment.objects.create(
                    ticket=ticket,
                    author=user,
                    comment_text=email_data['extracted_reply'] or email_data['body_text'] or description,
                    is_internal=False,
                    source=TicketComment.Source.EMAIL,
                    incoming_email=incoming_email,
                )

                incoming_email.mark_processed(ticket=ticket, user=user)

                logger.info(f"Created new ticket #{ticket.id} from email: {subject}")
                return True

        except Exception as e:
            logger.error(f"Failed to create new ticket from email: {e}")
            incoming_email.mark_failed(str(e))
            return False

    def _extract_email_data(self, msg: email.message.Message) -> Dict[str, Any]:
        """
        Extract relevant data from an email message.

        Args:
            msg: Parsed email message

        Returns:
            Dictionary with extracted email data
        """
        # Get message ID
        message_id = msg.get('Message-ID', '')
        if not message_id:
            # Generate a unique ID if none exists
            message_id = f"generated-{timezone.now().timestamp()}"

        # Parse From header
        from_name, from_address = parseaddr(msg.get('From', ''))
        from_name = self._decode_header(from_name)

        # Parse To header
        _, to_address = parseaddr(msg.get('To', ''))

        # Get subject
        subject = self._decode_header(msg.get('Subject', ''))

        # Get date
        date_str = msg.get('Date', '')
        try:
            email_date = parsedate_to_datetime(date_str)
        except Exception:
            email_date = timezone.now()

        # Extract body
        body_text, body_html = self._extract_body(msg)

        # Extract just the reply (remove quoted text)
        extracted_reply = self._extract_reply_text(body_text)

        # Try to extract ticket ID from To address or subject
        ticket_id = self._extract_ticket_id(to_address, subject)

        # Get relevant headers for debugging
        headers = {
            'from': msg.get('From', ''),
            'to': msg.get('To', ''),
            'subject': subject,
            'date': date_str,
            'message-id': message_id,
            'in-reply-to': msg.get('In-Reply-To', ''),
            'references': msg.get('References', ''),
            'x-ticket-id': msg.get('X-Ticket-ID', ''),
        }

        return {
            'message_id': message_id,
            'from_name': from_name,
            'from_address': from_address.lower(),
            'to_address': to_address.lower(),
            'subject': subject,
            'body_text': body_text,
            'body_html': body_html,
            'extracted_reply': extracted_reply,
            'date': email_date,
            'headers': headers,
            'ticket_id': ticket_id,
        }

    def _decode_header(self, header_value: str) -> str:
        """Decode an email header value."""
        if not header_value:
            return ''

        decoded_parts = decode_header(header_value)
        result = []

        for content, charset in decoded_parts:
            if isinstance(content, bytes):
                charset = charset or 'utf-8'
                try:
                    content = content.decode(charset)
                except Exception:
                    content = content.decode('utf-8', errors='replace')
            result.append(content)

        return ''.join(result)

    def _extract_body(self, msg: email.message.Message) -> Tuple[str, str]:
        """
        Extract text and HTML body from email.

        Returns:
            Tuple of (text_body, html_body)
        """
        text_body = ''
        html_body = ''

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

                # Skip attachments
                if 'attachment' in content_disposition:
                    continue

                try:
                    body = part.get_payload(decode=True)
                    if body:
                        charset = part.get_content_charset() or 'utf-8'
                        body = body.decode(charset, errors='replace')

                        if content_type == 'text/plain':
                            text_body = body
                        elif content_type == 'text/html':
                            html_body = body
                except Exception as e:
                    logger.warning(f"Error extracting email body part: {e}")
        else:
            # Single part message
            content_type = msg.get_content_type()
            try:
                body = msg.get_payload(decode=True)
                if body:
                    charset = msg.get_content_charset() or 'utf-8'
                    body = body.decode(charset, errors='replace')

                    if content_type == 'text/plain':
                        text_body = body
                    elif content_type == 'text/html':
                        html_body = body
            except Exception as e:
                logger.warning(f"Error extracting email body: {e}")

        # If no text body but have HTML, try to extract text from HTML
        if not text_body and html_body:
            text_body = self._html_to_text(html_body)

        return text_body, html_body

    def _html_to_text(self, html: str) -> str:
        """Convert HTML to plain text (basic implementation)."""
        import re

        # Remove script and style elements
        text = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL | re.IGNORECASE)
        text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL | re.IGNORECASE)

        # Replace <br> and <p> with newlines
        text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)
        text = re.sub(r'</p>', '\n\n', text, flags=re.IGNORECASE)

        # Remove all other HTML tags
        text = re.sub(r'<[^>]+>', '', text)

        # Decode HTML entities
        import html as html_module
        text = html_module.unescape(text)

        # Clean up whitespace
        text = re.sub(r'\n\s*\n', '\n\n', text)
        text = text.strip()

        return text

    def _extract_reply_text(self, text: str) -> str:
        """
        Extract just the reply portion, removing quoted text.

        Handles common reply formats:
        - Gmail: "On Date, Name <email> wrote:"
        - Outlook: "From: Name" / "-----Original Message-----"
        - Apple Mail: "On Date, at Time, Name wrote:"
        - Generic: Lines starting with ">"
        """
        if not text:
            return ''

        lines = text.split('\n')
        reply_lines = []

        # Patterns that indicate start of quoted content
        quote_patterns = [
            r'^On .+ wrote:$',  # Gmail style
            r'^On .+, at .+, .+ wrote:$',  # Apple Mail style
            r'^From:.*',  # Outlook style header
            r'^-{3,}\s*Original Message\s*-{3,}',  # Outlook separator
            r'^_{3,}',  # Underscores separator
            r'^\*From:\*',  # Formatted From:
            r'^Sent from my ',  # Mobile signatures
            r'^Get Outlook for ',  # Outlook signature
        ]

        for i, line in enumerate(lines):
            stripped = line.strip()

            # Check if this line starts quoted content
            is_quote_start = False
            for pattern in quote_patterns:
                if re.match(pattern, stripped, re.IGNORECASE):
                    is_quote_start = True
                    break

            if is_quote_start:
                # Stop here - everything after is quoted
                break

            # Skip lines that are just quote markers
            if stripped.startswith('>'):
                continue

            reply_lines.append(line)

        # Join and clean up
        reply = '\n'.join(reply_lines)

        # Remove trailing whitespace and signatures
        reply = re.sub(r'\n\s*--\s*\n.*$', '', reply, flags=re.DOTALL)
        reply = reply.strip()

        return reply

    def _extract_ticket_id(self, to_address: str, subject: str) -> str:
        """
        Extract ticket ID from email address or subject.

        Args:
            to_address: The To address of the email
            subject: The email subject

        Returns:
            Ticket ID as string, or empty string if not found
        """
        # Check To address first (most reliable)
        for pattern in self.TICKET_ID_PATTERNS:
            match = re.search(pattern, to_address, re.IGNORECASE)
            if match:
                return match.group(1)

        # Check subject line
        for pattern in self.TICKET_ID_PATTERNS:
            match = re.search(pattern, subject, re.IGNORECASE)
            if match:
                return match.group(1)

        return ''

    def _find_matching_ticket(self, email_data: Dict[str, Any]) -> Optional[Ticket]:
        """
        Find the ticket this email is replying to.

        Args:
            email_data: Extracted email data

        Returns:
            Matching Ticket or None
        """
        # First try by extracted ticket ID
        ticket_id = email_data.get('ticket_id')
        if ticket_id:
            try:
                ticket = Ticket.objects.get(id=int(ticket_id))
                logger.info(f"Matched email to ticket #{ticket_id} by ID")
                return ticket
            except (Ticket.DoesNotExist, ValueError):
                pass

        # Try by X-Ticket-ID header (from our outbound emails)
        x_ticket_id = email_data['headers'].get('x-ticket-id')
        if x_ticket_id:
            try:
                ticket = Ticket.objects.get(id=int(x_ticket_id))
                logger.info(f"Matched email to ticket #{x_ticket_id} by X-Ticket-ID header")
                return ticket
            except (Ticket.DoesNotExist, ValueError):
                pass

        # Try by In-Reply-To or References headers
        in_reply_to = email_data['headers'].get('in-reply-to', '')
        references = email_data['headers'].get('references', '')

        for ref in [in_reply_to, references]:
            # Look for ticket ID in the reference
            for pattern in self.TICKET_ID_PATTERNS:
                match = re.search(pattern, ref, re.IGNORECASE)
                if match:
                    try:
                        ticket_id = int(match.group(1))
                        ticket = Ticket.objects.get(id=ticket_id)
                        logger.info(f"Matched email to ticket #{ticket_id} by reference header")
                        return ticket
                    except (Ticket.DoesNotExist, ValueError):
                        pass

        # Last resort: find recent ticket by sender email
        from_address = email_data['from_address']
        try:
            # Find most recent open ticket created by this user
            user = User.objects.filter(email__iexact=from_address).first()
            if user:
                ticket = Ticket.objects.filter(
                    creator=user,
                    status__in=[
                        Ticket.Status.OPEN,
                        Ticket.Status.IN_PROGRESS,
                        Ticket.Status.AWAITING_RESPONSE
                    ]
                ).order_by('-created_at').first()

                if ticket:
                    logger.info(f"Matched email to ticket #{ticket.id} by sender's recent ticket")
                    return ticket
        except Exception as e:
            logger.warning(f"Error finding ticket by sender: {e}")

        return None

    def _find_user_by_email(self, email_address: str) -> Optional[User]:
        """Find a user by email address."""
        try:
            return User.objects.filter(email__iexact=email_address).first()
        except Exception:
            return None

    def _update_email_address_error(self, error: str):
        """Update email address with error message."""
        self.email_address.last_error = error
        self.email_address.last_check_at = timezone.now()
        self.email_address.save()

    def _delete_email(self, email_id: bytes):
        """
        Delete an email from the server.

        Args:
            email_id: IMAP email ID to delete
        """
        try:
            # Mark the email for deletion
            self.connection.store(email_id, '+FLAGS', '\\Deleted')
            # Permanently remove deleted messages
            self.connection.expunge()
            logger.info(f"Deleted email {email_id} from server")
        except Exception as e:
            logger.error(f"Failed to delete email {email_id}: {e}")


class PlatformEmailReceiver:
    """
    Service for receiving and processing inbound ticket emails from PlatformEmailAddress.
    Similar to TicketEmailReceiver but adapted for platform-managed email addresses.
    """

    # Patterns to extract ticket ID from email addresses
    TICKET_ID_PATTERNS = [
        r'ticket[_-](\d+)',  # ticket-123 or ticket_123
        r'\+ticket[_-](\d+)',  # +ticket-123 (subaddressing)
        r'reply[_-](\d+)',  # reply-123
        r'\[Ticket #(\d+)\]',  # [Ticket #123] in subject
        r'#(\d+)',  # #123 in subject (less specific)
    ]

    def __init__(self, email_address):
        """Initialize with a PlatformEmailAddress instance."""
        from smoothschedule.platform.admin.models import PlatformEmailAddress
        self.email_address = email_address
        self.connection = None

    def connect(self) -> bool:
        """Establish connection to IMAP server."""
        imap_settings = self.email_address.get_imap_settings()

        try:
            if imap_settings['use_ssl']:
                self.connection = imaplib.IMAP4_SSL(
                    imap_settings['host'],
                    imap_settings['port']
                )
            else:
                self.connection = imaplib.IMAP4(
                    imap_settings['host'],
                    imap_settings['port']
                )

            self.connection.login(
                imap_settings['username'],
                imap_settings['password']
            )

            logger.info(f"[Platform: {self.email_address.display_name}] Connected to IMAP server")
            return True

        except imaplib.IMAP4.error as e:
            logger.error(f"[Platform: {self.email_address.display_name}] IMAP login failed: {e}")
            self._update_error(f"IMAP login failed: {e}")
            return False
        except Exception as e:
            logger.error(f"[Platform: {self.email_address.display_name}] Failed to connect: {e}")
            self._update_error(f"Connection failed: {e}")
            return False

    def disconnect(self):
        """Close IMAP connection."""
        if self.connection:
            try:
                self.connection.logout()
            except Exception:
                pass
            self.connection = None

    def fetch_and_process_emails(self) -> int:
        """Fetch new emails and process them into tickets."""
        if not self.email_address.is_active:
            return 0

        if not self.connect():
            return 0

        processed_count = 0

        try:
            self.connection.select('INBOX')
            status, messages = self.connection.search(None, 'UNSEEN')

            if status != 'OK':
                logger.error(f"[Platform: {self.email_address.display_name}] Failed to search emails")
                return 0

            email_ids = messages[0].split()
            logger.info(f"[Platform: {self.email_address.display_name}] Found {len(email_ids)} unread emails")

            for email_id in email_ids:
                try:
                    if self._process_single_email(email_id):
                        processed_count += 1
                except Exception as e:
                    logger.error(f"[Platform: {self.email_address.display_name}] Error processing email {email_id}: {e}")

            # Update last check time
            self.email_address.last_check_at = timezone.now()
            self.email_address.emails_processed_count += processed_count
            self.email_address.save(update_fields=['last_check_at', 'emails_processed_count'])

        except Exception as e:
            logger.error(f"[Platform: {self.email_address.display_name}] Error fetching emails: {e}")
            self._update_error(str(e))
        finally:
            self.disconnect()

        return processed_count

    def _process_single_email(self, email_id: bytes) -> bool:
        """Process a single email message."""
        status, msg_data = self.connection.fetch(email_id, '(RFC822)')

        if status != 'OK':
            return False

        raw_email = msg_data[0][1]
        msg = email.message_from_bytes(raw_email)

        email_data = self._extract_email_data(msg)

        # Block emails sent to noreply@smoothschedule.com - just delete them
        if email_data['to_address'] == 'noreply@smoothschedule.com':
            logger.info(f"Deleting email sent to noreply@smoothschedule.com from {email_data['from_address']}")
            self._delete_email(email_id)
            return False

        # Check for duplicate
        if IncomingTicketEmail.objects.filter(message_id=email_data['message_id']).exists():
            logger.info(f"Duplicate email: {email_data['message_id']}")
            return False

        # Create incoming email record
        incoming_email = IncomingTicketEmail.objects.create(
            message_id=email_data['message_id'],
            from_address=email_data['from_address'],
            from_name=email_data['from_name'],
            to_address=email_data['to_address'],
            subject=email_data['subject'],
            body_text=email_data['body_text'],
            body_html=email_data['body_html'],
            extracted_reply=email_data['extracted_reply'],
            raw_headers=email_data['headers'],
            email_date=email_data['date'],
            ticket_id_from_email=email_data.get('ticket_id', ''),
        )

        # Try to match to existing ticket
        ticket = self._find_matching_ticket(email_data)
        user = self._find_user_by_email(email_data['from_address'])

        if not ticket:
            # Create new ticket
            success = self._create_new_ticket_from_email(email_data, incoming_email, user)
            if success:
                # Delete email from server after successful processing
                self._delete_email(email_id)
            return success

        # Add comment to existing ticket
        if not user:
            if ticket.creator and ticket.creator.email.lower() == email_data['from_address'].lower():
                user = ticket.creator
            elif ticket.assignee and ticket.assignee.email.lower() == email_data['from_address'].lower():
                user = ticket.assignee

        is_external_sender = False
        if not user and ticket.external_email:
            if ticket.external_email.lower() == email_data['from_address'].lower():
                is_external_sender = True

        if not user and not is_external_sender:
            logger.warning(f"Could not match user for email from {email_data['from_address']}")
            incoming_email.mark_failed("Could not match sender")
            return False

        try:
            with transaction.atomic():
                TicketComment.objects.create(
                    ticket=ticket,
                    author=user,
                    comment_text=email_data['extracted_reply'] or email_data['body_text'],
                    is_internal=False,
                    source=TicketComment.Source.EMAIL,
                    incoming_email=incoming_email,
                    external_author_email=email_data['from_address'] if is_external_sender else None,
                    external_author_name=email_data['from_name'] if is_external_sender else '',
                )

                if ticket.status == Ticket.Status.AWAITING_RESPONSE:
                    if user == ticket.creator or is_external_sender:
                        ticket.status = Ticket.Status.OPEN
                        ticket.save()

                incoming_email.mark_processed(ticket=ticket, user=user)
                # Delete email from server after successful processing
                self._delete_email(email_id)
                return True

        except Exception as e:
            logger.error(f"Failed to create comment: {e}")
            incoming_email.mark_failed(str(e))
            return False

    def _create_new_ticket_from_email(self, email_data, incoming_email, user) -> bool:
        """Create a new ticket from an incoming email."""
        try:
            with transaction.atomic():
                subject = email_data['subject'] or 'Email Support Request'
                subject = re.sub(r'^(Re|Fwd|FW|RE|FWD):\s*', '', subject, flags=re.IGNORECASE).strip()
                if not subject:
                    subject = 'Email Support Request'

                description = email_data['body_text'] or email_data['extracted_reply'] or ''

                ticket = Ticket.objects.create(
                    tenant=None,
                    creator=user,
                    assignee=None,
                    ticket_type=Ticket.TicketType.PLATFORM,
                    status=Ticket.Status.OPEN,
                    priority=Ticket.Priority.LOW,
                    category=Ticket.Category.GENERAL_INQUIRY,
                    subject=subject[:255],
                    description=description,
                    is_sandbox=False,
                    external_email=email_data['from_address'] if not user else None,
                    external_name=email_data['from_name'] if not user else '',
                    # Note: source_email_address is for TicketEmailAddress, not PlatformEmailAddress
                )

                TicketComment.objects.create(
                    ticket=ticket,
                    author=user,
                    comment_text=email_data['extracted_reply'] or email_data['body_text'] or description,
                    is_internal=False,
                    source=TicketComment.Source.EMAIL,
                    incoming_email=incoming_email,
                )

                incoming_email.mark_processed(ticket=ticket, user=user)
                logger.info(f"Created new ticket #{ticket.id} from email: {subject}")
                return True

        except Exception as e:
            logger.error(f"Failed to create ticket from email: {e}")
            incoming_email.mark_failed(str(e))
            return False

    def _extract_email_data(self, msg):
        """Extract relevant data from email message."""
        message_id = msg.get('Message-ID', '') or f"generated-{timezone.now().timestamp()}"

        from_name, from_address = parseaddr(msg.get('From', ''))
        from_name = self._decode_header(from_name)
        _, to_address = parseaddr(msg.get('To', ''))
        subject = self._decode_header(msg.get('Subject', ''))

        try:
            email_date = parsedate_to_datetime(msg.get('Date', ''))
        except Exception:
            email_date = timezone.now()

        body_text, body_html = self._extract_body(msg)
        extracted_reply = self._extract_reply_text(body_text)
        ticket_id = self._extract_ticket_id(to_address, subject)

        headers = {
            'from': msg.get('From', ''),
            'to': msg.get('To', ''),
            'subject': subject,
            'date': msg.get('Date', ''),
            'message-id': message_id,
            'in-reply-to': msg.get('In-Reply-To', ''),
            'references': msg.get('References', ''),
            'x-ticket-id': msg.get('X-Ticket-ID', ''),
        }

        return {
            'message_id': message_id,
            'from_name': from_name,
            'from_address': from_address.lower(),
            'to_address': to_address.lower(),
            'subject': subject,
            'body_text': body_text,
            'body_html': body_html,
            'extracted_reply': extracted_reply,
            'date': email_date,
            'headers': headers,
            'ticket_id': ticket_id,
        }

    def _decode_header(self, header_value: str) -> str:
        """Decode an email header value."""
        if not header_value:
            return ''
        decoded_parts = decode_header(header_value)
        result = []
        for content, charset in decoded_parts:
            if isinstance(content, bytes):
                charset = charset or 'utf-8'
                try:
                    content = content.decode(charset)
                except Exception:
                    content = content.decode('utf-8', errors='replace')
            result.append(content)
        return ''.join(result)

    def _extract_body(self, msg) -> Tuple[str, str]:
        """Extract text and HTML body from email."""
        text_body = ''
        html_body = ''

        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                if 'attachment' in str(part.get('Content-Disposition', '')):
                    continue
                try:
                    body = part.get_payload(decode=True)
                    if body:
                        charset = part.get_content_charset() or 'utf-8'
                        body = body.decode(charset, errors='replace')
                        if content_type == 'text/plain':
                            text_body = body
                        elif content_type == 'text/html':
                            html_body = body
                except Exception:
                    pass
        else:
            content_type = msg.get_content_type()
            try:
                body = msg.get_payload(decode=True)
                if body:
                    charset = msg.get_content_charset() or 'utf-8'
                    body = body.decode(charset, errors='replace')
                    if content_type == 'text/plain':
                        text_body = body
                    elif content_type == 'text/html':
                        html_body = body
            except Exception:
                pass

        if not text_body and html_body:
            text_body = self._html_to_text(html_body)

        return text_body, html_body

    def _html_to_text(self, html: str) -> str:
        """Convert HTML to plain text."""
        text = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL | re.IGNORECASE)
        text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL | re.IGNORECASE)
        text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)
        text = re.sub(r'</p>', '\n\n', text, flags=re.IGNORECASE)
        text = re.sub(r'<[^>]+>', '', text)
        import html as html_module
        text = html_module.unescape(text)
        text = re.sub(r'\n\s*\n', '\n\n', text)
        return text.strip()

    def _extract_reply_text(self, text: str) -> str:
        """Extract reply portion, removing quoted text."""
        if not text:
            return ''

        lines = text.split('\n')
        reply_lines = []

        quote_patterns = [
            r'^On .+ wrote:$',
            r'^On .+, at .+, .+ wrote:$',
            r'^From:.*',
            r'^-{3,}\s*Original Message\s*-{3,}',
            r'^_{3,}',
            r'^\*From:\*',
            r'^Sent from my ',
            r'^Get Outlook for ',
        ]

        for line in lines:
            stripped = line.strip()
            is_quote_start = any(re.match(p, stripped, re.IGNORECASE) for p in quote_patterns)
            if is_quote_start:
                break
            if stripped.startswith('>'):
                continue
            reply_lines.append(line)

        reply = '\n'.join(reply_lines)
        reply = re.sub(r'\n\s*--\s*\n.*$', '', reply, flags=re.DOTALL)
        return reply.strip()

    def _extract_ticket_id(self, to_address: str, subject: str) -> str:
        """Extract ticket ID from address or subject."""
        for pattern in self.TICKET_ID_PATTERNS:
            match = re.search(pattern, to_address, re.IGNORECASE)
            if match:
                return match.group(1)
        for pattern in self.TICKET_ID_PATTERNS:
            match = re.search(pattern, subject, re.IGNORECASE)
            if match:
                return match.group(1)
        return ''

    def _find_matching_ticket(self, email_data) -> Optional[Ticket]:
        """Find the ticket this email is replying to."""
        ticket_id = email_data.get('ticket_id')
        if ticket_id:
            try:
                return Ticket.objects.get(id=int(ticket_id))
            except (Ticket.DoesNotExist, ValueError):
                pass

        x_ticket_id = email_data['headers'].get('x-ticket-id')
        if x_ticket_id:
            try:
                return Ticket.objects.get(id=int(x_ticket_id))
            except (Ticket.DoesNotExist, ValueError):
                pass

        in_reply_to = email_data['headers'].get('in-reply-to', '')
        references = email_data['headers'].get('references', '')

        for ref in [in_reply_to, references]:
            for pattern in self.TICKET_ID_PATTERNS:
                match = re.search(pattern, ref, re.IGNORECASE)
                if match:
                    try:
                        return Ticket.objects.get(id=int(match.group(1)))
                    except (Ticket.DoesNotExist, ValueError):
                        pass

        return None

    def _find_user_by_email(self, email_address: str) -> Optional[User]:
        """Find a user by email address."""
        try:
            return User.objects.filter(email__iexact=email_address).first()
        except Exception:
            return None

    def _update_error(self, error: str):
        """Update email address with error message."""
        self.email_address.last_sync_error = error
        self.email_address.last_check_at = timezone.now()
        self.email_address.save(update_fields=['last_sync_error', 'last_check_at'])


def test_imap_connection() -> Tuple[bool, str]:
    """
    Test IMAP connection with current settings.

    Returns:
        Tuple of (success, message)
    """
    receiver = TicketEmailReceiver()

    if not receiver.settings.is_configured():
        return False, "IMAP settings not configured"

    try:
        if receiver.connect():
            # Try to select inbox
            status, _ = receiver.connection.select(receiver.settings.imap_folder)
            receiver.disconnect()

            if status == 'OK':
                return True, f"Successfully connected to {receiver.settings.imap_host}"
            else:
                return False, f"Could not access folder '{receiver.settings.imap_folder}'"
        else:
            return False, "Failed to connect to IMAP server"
    except Exception as e:
        return False, f"Connection error: {str(e)}"
