from flask import Flask, render_template, request, jsonify, session
import json
import os
import smtplib
import time
import threading
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import hashlib
try:
    from apscheduler.schedulers.background import BackgroundScheduler
    from apscheduler.jobstores.base import JobLookupError
    APSCHED_AVAILABLE = True
except Exception:
    BackgroundScheduler = None
    class JobLookupError(Exception):
        pass
    APSCHED_AVAILABLE = False

app = Flask(__name__)
# Sử dụng SECRET_KEY cố định để tránh lỗi session không hợp lệ giữa các worker/process
# Có thể đặt biến môi trường APP_SECRET_KEY trên server để tùy chỉnh.
app.secret_key = os.environ.get('APP_SECRET_KEY', 'f5d3b7f1b8a14d64a232b90a9e7d9a4e3c1c9d76a6f5b4c3e2d1f0a9b8c7d6e5')
app.permanent_session_lifetime = timedelta(days=7)

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# File lưu trữ cấu hình (dùng đường dẫn tuyệt đối để tránh sai thư mục làm việc)
CONFIG_FILE = os.path.join(BASE_DIR, 'mail_configs.json')
LOGS_FILE = os.path.join(BASE_DIR, 'mail_logs.json')

# Dictionary để lưu các thread đang chạy
running_threads = {}
scheduled_jobs = {}

# Mật khẩu bảo vệ trang web (thay đổi mật khẩu này)
ADMIN_PASSWORD_HASH = hashlib.sha256("vukhanhlinh2024".encode()).hexdigest()  # Đổi mật khẩu ở đây

def load_configs():
    """Đọc cấu hình từ file"""
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return []

def save_configs(configs):
    """Lưu cấu hình vào file"""
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(configs, f, ensure_ascii=False, indent=2)

def load_logs():
    """Đọc logs từ file"""
    if os.path.exists(LOGS_FILE):
        with open(LOGS_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return []

def save_log(log_entry):
    """Lưu log vào file"""
    logs = load_logs()
    logs.append(log_entry)
    # Giữ tối đa 1000 logs
    if len(logs) > 1000:
        logs = logs[-1000:]
    with open(LOGS_FILE, 'w', encoding='utf-8') as f:
        json.dump(logs, f, ensure_ascii=False, indent=2)

def get_config_upload_folder(config_id):
    """Lấy đường dẫn folder upload cho config"""
    folder = os.path.join(BASE_DIR, 'uploads', f'{config_id}')
    if not os.path.exists(folder):
        os.makedirs(folder)
    return folder

def reload_active_configs():
    """Reload lịch cho các config đang active khi app khởi động"""
    try:
        configs = load_configs()
        for config in configs:
            if config.get('active', False) and config.get('start_time'):
                try:
                    schedule_jobs_for_config(config['id'])
                except Exception as e:
                    print(f"[RELOAD] Error scheduling config {config.get('id')}: {str(e)}")
    except Exception as e:
        print(f"[RELOAD] Error in reload_active_configs: {str(e)}")

def get_scheduled_times_from_config(config):
    start_time_str = config.get('start_time', '')
    if not start_time_str:
        return []
    start_time = datetime.strptime(start_time_str, '%Y-%m-%d %H:%M')
    num_sends = config.get('num_sends', 1)
    interval_minutes = config.get('interval_minutes', 0)
    times = []
    current = start_time
    for _ in range(num_sends):
        times.append(current)
        current += timedelta(minutes=interval_minutes)
    return times

def scheduled_send_job(config_id, send_index=None):
    configs = load_configs()
    config = next((c for c in configs if c['id'] == config_id), None)
    if not config or not config.get('active', False):
        print(f"[JOB] Skipped send for {config_id}: inactive or missing")
        return
    print(f"[JOB] Trigger send for config {config_id}, index={send_index}")
    send_email_now(config_id, send_index=send_index)

def schedule_jobs_for_config(config_id):
    configs = load_configs()
    config = next((c for c in configs if c['id'] == config_id), None)
    if not config:
        return
    cancel_jobs_for_config(config_id)

    times = get_scheduled_times_from_config(config)
    if not times:
        return

    job_ids = []
    now = datetime.now()
    for idx, run_time in enumerate(times, 1):
        if run_time <= now:
            print(f"[SCHED] Past time for {config_id} -> send now with index {idx}")
            scheduled_send_job(config_id, send_index=idx)
            continue
        job_id = f"{config_id}-{run_time.strftime('%Y%m%d%H%M')}-{idx}"
        scheduler.add_job(
            func=scheduled_send_job,
            trigger='date',
            run_date=run_time,
            args=[config_id, idx],
            id=job_id,
            replace_existing=True
        )
        job_ids.append(job_id)
        print(f"[SCHED] Scheduled job {job_id} at {run_time} with index {idx}")

    if job_ids:
        scheduled_jobs[config_id] = job_ids

def cancel_jobs_for_config(config_id):
    job_ids = scheduled_jobs.get(config_id, [])
    for jid in job_ids:
        try:
            scheduler.remove_job(jid)
        except JobLookupError:
            pass
    if config_id in scheduled_jobs:
        del scheduled_jobs[config_id]

def send_email_now(config_id, send_index=None):
    """Gửi email ngay lập tức - không check active. Trả về (success, error_message)
    
    Args:
        config_id: ID của cấu hình
        send_index: Số thứ tự lần gửi (1, 2, 3...). Nếu có, sẽ thêm số vào tiêu đề
    """
    print(f"[SEND] send_email_now called for config {config_id}, index={send_index}")
    configs = load_configs()
    config = None
    for c in configs:
        if c['id'] == config_id:
            config = c
            break
    
    if not config:
        print(f"[SEND] Config {config_id} not found")
        return False, "Không tìm thấy cấu hình"
    
    try:
        gmail = config['gmail']
        password = config['password']
        recipients = [e.strip() for e in config['recipients'].split(',')]
        subject = config['subject']
        content = config['content']
        
        # Thêm số thứ tự vào tiêu đề nếu có send_index
        if send_index is not None:
            subject = f"{subject} {send_index}"
        
        msg = MIMEMultipart()
        msg['From'] = gmail
        msg['To'] = ', '.join(recipients)
        msg['Subject'] = subject
        
        msg.attach(MIMEText(content, 'plain', 'utf-8'))
        
        # Đính kèm files của config này
        upload_folder = get_config_upload_folder(config_id)
        if os.path.exists(upload_folder):
            files = [f for f in os.listdir(upload_folder) 
                   if os.path.isfile(os.path.join(upload_folder, f))]
            
            for filename in files:
                filepath = os.path.join(upload_folder, filename)
                try:
                    with open(filepath, 'rb') as attachment:
                        part = MIMEBase('application', 'octet-stream')
                        part.set_payload(attachment.read())
                    encoders.encode_base64(part)
                    part.add_header('Content-Disposition', f'attachment; filename= {filename}')
                    msg.attach(part)
                except Exception as e:
                    print(f"Error attaching {filename}: {str(e)}")
        
        # Gửi email (ổn định hơn với EHLO và sendmail, có timeout)
        with smtplib.SMTP('smtp.gmail.com', 587, timeout=30) as server:
            server.ehlo()
            server.starttls()
            server.ehlo()
            server.login(gmail, password)
            server.sendmail(gmail, recipients, msg.as_string())
        
        # Lưu log
        log_entry = {
            'config_id': config_id,
            'config_name': config.get('name', 'N/A'),
            'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'status': 'success',
            'recipients': ', '.join(recipients),
            'subject': subject
        }
        save_log(log_entry)
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Email sent from {gmail} to {', '.join(recipients)}")
        return True, None
        
    except Exception as e:
        error_msg = str(e)
        log_entry = {
            'config_id': config_id,
            'config_name': config.get('name', 'N/A') if config else 'N/A',
            'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'status': 'error',
            'error': error_msg
        }
        save_log(log_entry)
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Error sending email from config {config_id}: {error_msg}")
        return False, error_msg

def check_auth():
    """Kiểm tra đăng nhập"""
    return session.get('authenticated', False)

@app.route('/')
def index():
    """Trang chủ"""
    if not check_auth():
        return render_template('login.html')
    
    configs = load_configs()
    logs = load_logs()[-50:]
    logs.reverse()
    return render_template('index.html', configs=configs, logs=logs)

@app.route('/login', methods=['POST'])
def login():
    """Xử lý đăng nhập"""
    password = request.form.get('password', '')
    password_hash = hashlib.sha256(password.encode()).hexdigest()
    
    if password_hash == ADMIN_PASSWORD_HASH:
        session.permanent = True
        session['authenticated'] = True
        return jsonify({'success': True})
    else:
        return jsonify({'success': False, 'error': 'Mật khẩu không đúng'})

@app.route('/logout')
def logout():
    """Đăng xuất"""
    session.pop('authenticated', None)
    return jsonify({'success': True})

@app.route('/api/configs', methods=['GET'])
def get_configs():
    """Lấy danh sách cấu hình"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    configs = load_configs()
    return jsonify(configs)

@app.route('/api/config/<config_id>', methods=['GET'])
def get_config(config_id):
    """Lấy chi tiết một cấu hình"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    configs = load_configs()
    for config in configs:
        if config['id'] == config_id:
            return jsonify(config)
    return jsonify({'error': 'Not found'}), 404

@app.route('/api/config', methods=['POST'])
def add_config():
    """Thêm cấu hình mới"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    data = request.json
    configs = load_configs()
    
    new_id = str(int(time.time() * 1000))
    
    new_config = {
        'id': new_id,
        'name': data.get('name', 'Cấu hình mới'),
        'gmail': data.get('gmail', ''),
        'password': data.get('password', ''),
        'recipients': data.get('recipients', ''),
        'subject': data.get('subject', ''),
        'content': data.get('content', ''),
        'start_time': data.get('start_time', ''),
        'interval_minutes': data.get('interval_minutes', 0),
        'repeat': data.get('repeat', False),
        'num_sends': data.get('num_sends', 1),
        'active': data.get('active', False),
        'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    }
    
    configs.append(new_config)
    save_configs(configs)
    
    return jsonify({'success': True, 'config': new_config})

@app.route('/api/config/<config_id>', methods=['PUT'])
def update_config(config_id):
    """Cập nhật cấu hình"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    data = request.json
    configs = load_configs()
    
    for i, config in enumerate(configs):
        if config['id'] == config_id:
            configs[i].update({
                'name': data.get('name', config['name']),
                'gmail': data.get('gmail', config['gmail']),
                'password': data.get('password', config['password']),
                'recipients': data.get('recipients', config['recipients']),
                'subject': data.get('subject', config['subject']),
                'content': data.get('content', config['content']),
                'start_time': data.get('start_time', config.get('start_time', '')),
                'interval_minutes': data.get('interval_minutes', config.get('interval_minutes', 0)),
                'repeat': data.get('repeat', config.get('repeat', False)),
                'num_sends': data.get('num_sends', config.get('num_sends', 1)),
                'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            })
            save_configs(configs)
            return jsonify({'success': True, 'config': configs[i]})
    
    return jsonify({'error': 'Not found'}), 404

@app.route('/api/config/<config_id>', methods=['DELETE'])
def delete_config(config_id):
    """Xóa cấu hình"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    configs = load_configs()
    cancel_jobs_for_config(config_id)
    if config_id in running_threads:
        print(f"[DELETE] Config {config_id} deleted, thread will stop")
    
    configs = [c for c in configs if c['id'] != config_id]
    save_configs(configs)
    
    return jsonify({'success': True})

@app.route('/api/config/<config_id>/toggle', methods=['POST'])
def toggle_config(config_id):
    """Bật/tắt cấu hình"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    configs = load_configs()
    
    for idx, config in enumerate(configs):
        if config['id'] == config_id:
            configs[idx]['active'] = not config.get('active', False)
            
            if configs[idx]['active']:
                start_time_str = config.get('start_time', '')
                if start_time_str:
                    try:
                        schedule_jobs_for_config(config_id)
                        sched_times = get_scheduled_times_from_config(configs[idx])
                        now_dt = datetime.now()
                        next_time = next((t for t in sched_times if t >= now_dt), None)
                        configs[idx]['next_run_time'] = next_time.strftime('%Y-%m-%d %H:%M') if next_time else None
                        print(f"[TOGGLE] Scheduled jobs for config {config_id}")
                    except Exception as e:
                        return jsonify({'success': False, 'error': str(e)})
            else:
                cancel_jobs_for_config(config_id)
                configs[idx]['next_run_time'] = None
                print(f"[TOGGLE] Deactivated config {config_id}, canceled jobs")
            
            save_configs(configs)
            return jsonify({'success': True, 'config': configs[idx]})
    
    return jsonify({'error': 'Not found'}), 404

@app.route('/api/config/<config_id>/upload', methods=['POST'])
def upload_file(config_id):
    """Upload file cho config"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    files = request.files.getlist('files')
    if not files:
        single = request.files.get('file')
        if single:
            files = [single]
    if not files:
        return jsonify({'error': 'No file provided'}), 400
    
    uploaded = []
    errors = []
    
    try:
        upload_folder = get_config_upload_folder(config_id)
        
        for file in files:
            if file.filename == '':
                continue
                
            try:
                filename = file.filename
                filepath = os.path.join(upload_folder, filename)
                file.save(filepath)
                
                uploaded.append({
                    'filename': filename,
                    'size': os.path.getsize(filepath)
                })
            except Exception as e:
                errors.append(f'{file.filename}: {str(e)}')
        
        return jsonify({
            'success': True,
            'uploaded': uploaded,
            'errors': errors
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/config/<config_id>/files', methods=['GET'])
def get_config_files(config_id):
    """Lấy danh sách files của config"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    upload_folder = get_config_upload_folder(config_id)
    files = []
    
    if os.path.exists(upload_folder):
        for filename in os.listdir(upload_folder):
            filepath = os.path.join(upload_folder, filename)
            if os.path.isfile(filepath):
                files.append({
                    'name': filename,
                    'size': os.path.getsize(filepath)
                })
    
    return jsonify(files)

@app.route('/api/config/<config_id>/files/<filename>', methods=['DELETE'])
def delete_config_file(config_id, filename):
    """Xóa file của config"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    try:
        upload_folder = get_config_upload_folder(config_id)
        filepath = os.path.join(upload_folder, filename)
        
        if os.path.exists(filepath):
            os.remove(filepath)
            return jsonify({'success': True})
        else:
            return jsonify({'error': 'File not found'}), 404
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/config/<config_id>/send-now', methods=['POST'])
def send_now(config_id):
    """Gửi email ngay lập tức - đồng bộ để trả lời chi tiết"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    try:
        success, error_msg = send_email_now(config_id)
        if success:
            return jsonify({'success': True, 'message': 'Email đã được gửi thành công!'})
        else:
            return jsonify({'success': False, 'error': error_msg or 'Không thể gửi email'})
    except Exception as e:
        return jsonify({'success': False, 'error': f'Lỗi hệ thống: {str(e)}'})

@app.route('/api/config/<config_id>/test-smtp', methods=['POST'])
def test_smtp(config_id):
    """Kiểm tra kết nối SMTP"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    configs = load_configs()
    config = next((c for c in configs if c['id'] == config_id), None)
    
    if not config:
        return jsonify({'success': False, 'error': 'Không tìm thấy cấu hình'})
    
    try:
        gmail = config['gmail']
        password = config['password']
        
        print(f"[TEST] Testing SMTP connection for {gmail}...")
        with smtplib.SMTP('smtp.gmail.com', 587, timeout=30) as server:
            server.ehlo()
            server.starttls()
            server.ehlo()
            server.login(gmail, password)
        
        return jsonify({
            'success': True, 
            'message': f'✅ Kết nối SMTP thành công!\nEmail: {gmail}\nSMTP: smtp.gmail.com:587'
        })
    except smtplib.SMTPAuthenticationError as e:
        return jsonify({
            'success': False, 
            'error': f'❌ Lỗi xác thực Gmail:\n{str(e)}\n\nĐảm bảo bạn đã:\n1. Bật xác thực 2 bước\n2. Tạo App Password (không dùng mật khẩu Gmail thường)\n3. Dùng App Password 16 ký tự'
        })
    except Exception as e:
        return jsonify({'success': False, 'error': f'❌ Lỗi kết nối: {str(e)}'})

@app.route('/api/logs', methods=['GET'])
def get_logs():
    """Lấy danh sách logs"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    logs = load_logs()[-100:]
    logs.reverse()
    return jsonify(logs)

@app.route('/api/scheduler-status', methods=['GET'])
def scheduler_status():
    """Kiểm tra trạng thái scheduler và các job đang chạy"""
    if not check_auth():
        return jsonify({'error': 'Unauthorized'}), 401
    
    status = {
        'scheduler_type': 'APScheduler' if APSCHED_AVAILABLE else 'SimpleScheduler',
        'active_jobs': {},
        'total_jobs': 0
    }
    
    for config_id, job_list in scheduled_jobs.items():
        status['active_jobs'][config_id] = len(job_list)
        status['total_jobs'] += len(job_list)
    
    return jsonify(status)

# Khởi tạo Scheduler
if APSCHED_AVAILABLE and BackgroundScheduler is not None:
    scheduler = BackgroundScheduler()
    scheduler.start()
    print("[SCHEDULER] APScheduler initialized and started")
else:
    class SimpleScheduler:
        def __init__(self):
            self._timers = {}

        def add_job(self, func, trigger, run_date, args=None, id=None, replace_existing=False):
            if replace_existing and id in self._timers:
                self.remove_job(id)
            delay = max(0, (run_date - datetime.now()).total_seconds())
            t = threading.Timer(delay, func, args=args or [])
            t.daemon = True
            t.start()
            if id:
                self._timers[id] = t

        def remove_job(self, id):
            t = self._timers.pop(id, None)
            if t:
                try:
                    t.cancel()
                except Exception:
                    pass

        def start(self):
            pass

    scheduler = SimpleScheduler()
    print("[SCHEDULER] SimpleScheduler initialized")

reload_active_configs()

if __name__ == '__main__':
    print("="*60)
    print(" EMAIL SCHEDULER WEB APP")
    print(" Server chạy độc lập với trình duyệt")
    print(" Scheduler sẽ hoạt động kể cả khi đóng trình duyệt")
    print("="*60)
    app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)
