"""
introd/deliver.py - intro delivery via multiple channels

supports:
- email (smtp)
- mastodon dm (if they allow dms)
- bluesky dm (via AT Protocol)
- matrix dm (creates DM room and sends message)
- github issue (opens intro as issue on their most active repo)
- manual queue (for review before sending)

contact method is determined by ACTIVITY-BASED SELECTION:
- picks the platform where the user is MOST ACTIVE
- verified handles (from rel="me" links) get a bonus

NOTE: reddit is NOT a delivery method - it's discovery only.
reddit-discovered users are contacted via their external links.
"""

import os
import json
import smtplib
import requests
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
from pathlib import Path

# config from env - no hardcoded credentials
SMTP_HOST = os.environ.get('SMTP_HOST', '')
SMTP_PORT = int(os.environ.get('SMTP_PORT', 465))
SMTP_USER = os.environ.get('SMTP_USER', '')
SMTP_PASS = os.environ.get('SMTP_PASS', '')
FROM_EMAIL = os.environ.get('FROM_EMAIL', '')

GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN', '')
MASTODON_TOKEN = os.environ.get('MASTODON_TOKEN', '')
MASTODON_INSTANCE = os.environ.get('MASTODON_INSTANCE', '')
BLUESKY_HANDLE = os.environ.get('BLUESKY_HANDLE', '')
BLUESKY_APP_PASSWORD = os.environ.get('BLUESKY_APP_PASSWORD', '')
MATRIX_HOMESERVER = os.environ.get('MATRIX_HOMESERVER', '')
MATRIX_USER_ID = os.environ.get('MATRIX_USER_ID', '')
MATRIX_ACCESS_TOKEN = os.environ.get('MATRIX_ACCESS_TOKEN', '')

# delivery log
DELIVERY_LOG = Path(__file__).parent.parent / 'data' / 'delivery_log.json'
MANUAL_QUEUE = Path(__file__).parent.parent / 'data' / 'manual_queue.json'


def load_delivery_log():
    """load delivery history"""
    if DELIVERY_LOG.exists():
        return json.loads(DELIVERY_LOG.read_text())
    return {'sent': [], 'failed': [], 'queued': []}


def save_delivery_log(log):
    """save delivery history"""
    DELIVERY_LOG.parent.mkdir(parents=True, exist_ok=True)
    DELIVERY_LOG.write_text(json.dumps(log, indent=2))


def load_manual_queue():
    """load manual review queue"""
    if MANUAL_QUEUE.exists():
        return json.loads(MANUAL_QUEUE.read_text())
    return []


def save_manual_queue(queue):
    """save manual review queue"""
    MANUAL_QUEUE.parent.mkdir(parents=True, exist_ok=True)
    MANUAL_QUEUE.write_text(json.dumps(queue, indent=2))


def already_contacted(recipient_id):
    """check if we've already sent an intro to this person"""
    log = load_delivery_log()
    sent_ids = [s.get('recipient_id') for s in log.get('sent', [])]
    return recipient_id in sent_ids


def send_email(to_email, subject, body, dry_run=False):
    """send email via smtp"""
    if dry_run:
        print(f"  [dry run] would email {to_email}")
        print(f"    subject: {subject}")
        print(f"    body preview: {body[:100]}...")
        return True, "dry run"

    try:
        msg = MIMEMultipart('alternative')
        msg['Subject'] = subject
        msg['From'] = FROM_EMAIL
        msg['To'] = to_email

        # plain text
        text_part = MIMEText(body, 'plain')
        msg.attach(text_part)

        # html version (simple)
        html_body = body.replace('\n', '<br>')
        html_part = MIMEText(f"<html><body><p>{html_body}</p></body></html>", 'html')
        msg.attach(html_part)

        with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server:
            server.login(SMTP_USER, SMTP_PASS)
            server.sendmail(SMTP_USER, to_email, msg.as_string())

        return True, None
    except Exception as e:
        return False, str(e)


def create_github_issue(owner, repo, title, body, dry_run=False):
    """create github issue as intro"""
    if not GITHUB_TOKEN:
        return False, "GITHUB_TOKEN not set"

    if dry_run:
        print(f"  [dry run] would create issue on {owner}/{repo}")
        print(f"    title: {title}")
        return True, "dry run"

    try:
        url = f"https://api.github.com/repos/{owner}/{repo}/issues"
        resp = requests.post(
            url,
            headers={
                'Authorization': f'token {GITHUB_TOKEN}',
                'Accept': 'application/vnd.github.v3+json',
            },
            json={
                'title': title,
                'body': body,
                'labels': ['introduction', 'community'],
            },
            timeout=30,
        )

        if resp.status_code == 201:
            issue_url = resp.json().get('html_url')
            return True, issue_url
        else:
            return False, f"github api error: {resp.status_code} - {resp.text}"
    except Exception as e:
        return False, str(e)


def send_mastodon_dm(recipient_acct, message, dry_run=False):
    """send mastodon direct message"""
    if not MASTODON_TOKEN:
        return False, "MASTODON_TOKEN not set"

    if dry_run:
        print(f"  [dry run] would DM {recipient_acct}")
        print(f"    message preview: {message[:100]}...")
        return True, "dry run"

    try:
        # post as direct message (visibility: direct, mention recipient)
        url = f"https://{MASTODON_INSTANCE}/api/v1/statuses"
        resp = requests.post(
            url,
            headers={
                'Authorization': f'Bearer {MASTODON_TOKEN}',
                'Content-Type': 'application/json',
            },
            json={
                'status': f"@{recipient_acct} {message}",
                'visibility': 'direct',
            },
            timeout=30,
        )

        if resp.status_code in [200, 201]:
            return True, resp.json().get('url')
        else:
            return False, f"mastodon api error: {resp.status_code} - {resp.text}"
    except Exception as e:
        return False, str(e)


def send_bluesky_dm(recipient_handle, message, dry_run=False):
    """send bluesky direct message via AT Protocol"""
    if not BLUESKY_APP_PASSWORD:
        return False, "BLUESKY_APP_PASSWORD not set"

    if dry_run:
        print(f"  [dry run] would DM {recipient_handle} on bluesky")
        print(f"    message preview: {message[:100]}...")
        return True, "dry run"

    try:
        # authenticate with bluesky
        auth_url = "https://bsky.social/xrpc/com.atproto.server.createSession"
        auth_resp = requests.post(
            auth_url,
            json={
                'identifier': BLUESKY_HANDLE,
                'password': BLUESKY_APP_PASSWORD,
            },
            timeout=30,
        )

        if auth_resp.status_code != 200:
            return False, f"bluesky auth failed: {auth_resp.status_code}"

        auth_data = auth_resp.json()
        access_token = auth_data.get('accessJwt')
        did = auth_data.get('did')

        # resolve recipient DID
        resolve_url = f"https://bsky.social/xrpc/com.atproto.identity.resolveHandle"
        resolve_resp = requests.get(
            resolve_url,
            params={'handle': recipient_handle.lstrip('@')},
            timeout=30,
        )

        if resolve_resp.status_code != 200:
            return False, f"couldn't resolve handle {recipient_handle}"

        recipient_did = resolve_resp.json().get('did')

        # create chat/DM (using convo namespace)
        # first get or create conversation
        convo_url = "https://bsky.social/xrpc/chat.bsky.convo.getConvoForMembers"
        convo_resp = requests.get(
            convo_url,
            headers={'Authorization': f'Bearer {access_token}'},
            params={'members': [recipient_did]},
            timeout=30,
        )

        if convo_resp.status_code != 200:
            # try creating conversation
            return False, f"couldn't get/create conversation: {convo_resp.status_code}"

        convo_id = convo_resp.json().get('convo', {}).get('id')

        # send message
        msg_url = "https://bsky.social/xrpc/chat.bsky.convo.sendMessage"
        msg_resp = requests.post(
            msg_url,
            headers={
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json',
            },
            json={
                'convoId': convo_id,
                'message': {'text': message},
            },
            timeout=30,
        )

        if msg_resp.status_code in [200, 201]:
            return True, f"sent to {recipient_handle}"
        else:
            return False, f"bluesky dm failed: {msg_resp.status_code} - {msg_resp.text}"

    except Exception as e:
        return False, str(e)


def send_matrix_dm(recipient_mxid, message, dry_run=False):
    """send matrix direct message"""
    if not MATRIX_ACCESS_TOKEN:
        return False, "MATRIX_ACCESS_TOKEN not set"

    if dry_run:
        print(f"  [dry run] would DM {recipient_mxid} on matrix")
        print(f"    message preview: {message[:100]}...")
        return True, "dry run"

    try:
        # create or get direct room with recipient
        # first, check if we already have a DM room
        headers = {'Authorization': f'Bearer {MATRIX_ACCESS_TOKEN}'}

        # create a new DM room
        create_room_resp = requests.post(
            f'{MATRIX_HOMESERVER}/_matrix/client/v3/createRoom',
            headers=headers,
            json={
                'is_direct': True,
                'invite': [recipient_mxid],
                'preset': 'trusted_private_chat',
            },
            timeout=30,
        )

        if create_room_resp.status_code not in [200, 201]:
            return False, f"matrix room creation failed: {create_room_resp.status_code} - {create_room_resp.text}"

        room_id = create_room_resp.json().get('room_id')

        # send message to room
        import time
        txn_id = str(int(time.time() * 1000))

        msg_resp = requests.put(
            f'{MATRIX_HOMESERVER}/_matrix/client/v3/rooms/{room_id}/send/m.room.message/{txn_id}',
            headers=headers,
            json={
                'msgtype': 'm.text',
                'body': message,
            },
            timeout=30,
        )

        if msg_resp.status_code in [200, 201]:
            return True, f"sent to {recipient_mxid} in {room_id}"
        else:
            return False, f"matrix send failed: {msg_resp.status_code} - {msg_resp.text}"

    except Exception as e:
        return False, str(e)


def add_to_manual_queue(intro_data):
    """add intro to manual review queue"""
    queue = load_manual_queue()
    queue.append({
        **intro_data,
        'queued_at': datetime.now().isoformat(),
        'status': 'pending',
    })
    save_manual_queue(queue)
    return True


def determine_best_contact(human):
    """
    determine best contact method based on WHERE THEY'RE MOST ACTIVE

    uses activity-based selection from groq_draft module
    """
    from introd.groq_draft import determine_contact_method as activity_based_contact

    method, info = activity_based_contact(human)

    # convert github_issue info to dict format for delivery
    if method == 'github_issue' and isinstance(info, str) and '/' in info:
        parts = info.split('/', 1)
        return method, {'owner': parts[0], 'repo': parts[1]}

    return method, info


def deliver_intro(match_data, intro_draft, dry_run=False):
    """
    deliver an intro via the best available method

    match_data: {human_a, human_b, overlap_score, overlap_reasons}
    intro_draft: the text to send (from groq)
    """
    recipient = match_data.get('human_b', {})
    recipient_id = f"{recipient.get('platform')}:{recipient.get('username')}"

    # check if already contacted
    if already_contacted(recipient_id):
        return False, "already contacted", None

    # determine contact method
    method, contact_info = determine_best_contact(recipient)

    log = load_delivery_log()
    result = {
        'recipient_id': recipient_id,
        'recipient_name': recipient.get('name') or recipient.get('username'),
        'method': method,
        'contact_info': contact_info,
        'overlap_score': match_data.get('overlap_score'),
        'timestamp': datetime.now().isoformat(),
    }

    success = False
    error = None

    if method == 'email':
        subject = f"someone you might want to know - connectd"
        success, error = send_email(contact_info, subject, intro_draft, dry_run)

    elif method == 'mastodon':
        success, error = send_mastodon_dm(contact_info, intro_draft, dry_run)

    elif method == 'bluesky':
        success, error = send_bluesky_dm(contact_info, intro_draft, dry_run)

    elif method == 'matrix':
        success, error = send_matrix_dm(contact_info, intro_draft, dry_run)

    elif method == 'discord':
        from scoutd.discord import send_discord_dm
        success, error = send_discord_dm(contact_info, intro_draft, dry_run)

    elif method == 'lemmy':
        from scoutd.lemmy import send_lemmy_dm
        success, error = send_lemmy_dm(contact_info, intro_draft, dry_run)

    elif method == 'github_issue':
        owner = contact_info.get('owner')
        repo = contact_info.get('repo')
        title = "community introduction from connectd"
        # format for github
        github_body = f"""hey {recipient.get('name') or recipient.get('username')},

{intro_draft}

---
*this is an automated introduction from [connectd](https://github.com/connectd-daemon), a daemon that finds isolated builders with aligned values and connects them. if this feels spammy, i apologize - you can close this issue and we won't reach out again.*
"""
        success, error = create_github_issue(owner, repo, title, github_body, dry_run)

    elif method == 'manual':
        # add to review queue
        add_to_manual_queue({
            'match': match_data,
            'draft': intro_draft,
            'recipient': recipient,
        })
        success = True
        error = "added to manual queue"

    # log result
    result['success'] = success
    result['error'] = error

    if success:
        log['sent'].append(result)
    else:
        log['failed'].append(result)

    save_delivery_log(log)

    return success, error, method


def deliver_batch(matches_with_intros, dry_run=False):
    """
    deliver intros for a batch of matches

    matches_with_intros: list of {match_data, intro_draft}
    """
    results = []

    for item in matches_with_intros:
        match_data = item.get('match_data') or item.get('match')
        intro_draft = item.get('intro_draft') or item.get('draft')

        if not match_data or not intro_draft:
            continue

        success, error, method = deliver_intro(match_data, intro_draft, dry_run)
        results.append({
            'recipient': match_data.get('human_b', {}).get('username'),
            'method': method,
            'success': success,
            'error': error,
        })

        print(f"  {match_data.get('human_b', {}).get('username')}: {method} - {'ok' if success else error}")

    return results


def get_delivery_stats():
    """get delivery statistics"""
    log = load_delivery_log()
    queue = load_manual_queue()

    return {
        'sent': len(log.get('sent', [])),
        'failed': len(log.get('failed', [])),
        'queued': len(log.get('queued', [])),
        'manual_pending': len([q for q in queue if q.get('status') == 'pending']),
        'by_method': {
            'email': len([s for s in log.get('sent', []) if s.get('method') == 'email']),
            'mastodon': len([s for s in log.get('sent', []) if s.get('method') == 'mastodon']),
            'github_issue': len([s for s in log.get('sent', []) if s.get('method') == 'github_issue']),
            'manual': len([s for s in log.get('sent', []) if s.get('method') == 'manual']),
        },
    }


def review_manual_queue():
    """review and process manual queue"""
    queue = load_manual_queue()
    pending = [q for q in queue if q.get('status') == 'pending']

    if not pending:
        print("no items in manual queue")
        return

    print(f"\n{len(pending)} items pending review:\n")

    for i, item in enumerate(pending, 1):
        recipient = item.get('recipient', {})
        match = item.get('match', {})

        print(f"[{i}] {recipient.get('name') or recipient.get('username')}")
        print(f"    platform: {recipient.get('platform')}")
        print(f"    url: {recipient.get('url')}")
        print(f"    overlap: {match.get('overlap_score')}")
        print(f"    draft preview: {item.get('draft', '')[:80]}...")
        print()

    return pending
