from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from datetime import datetime, timedelta
from io import BytesIO
import os
import uuid
import qrcode

from config import Config
from models import db, User, Donation, Event, EventSubscription

app = Flask(__name__, template_folder='templates', static_folder='static')
app.config.from_object(Config)

# Initialize extensions
db.init_app(app)
jwt = JWTManager(app)
CORS(app)

# Create tables
with app.app_context():
    db.create_all()

# ==========================
# AUTHENTICATION ENDPOINTS
# ==========================

@app.route('/api/auth/register', methods=['POST'])
def register():
    data = request.get_json()

    required = ['email', 'password', 'full_name', 'phone', 'blood_group', 
                'city', 'location', 'age', 'weight', 'gender']

    for field in required:
        if not data.get(field):
            return jsonify({'error': f'{field} is required'}), 400

    if User.query.filter_by(email=data['email'].lower()).first():
        return jsonify({'error': 'Email already registered'}), 409

    valid_blood_groups = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']
    if data['blood_group'] not in valid_blood_groups:
        return jsonify({'error': f'Invalid blood group. Must be one of: {", ".join(valid_blood_groups)}'}), 400

    try:
        user = User(
            email=data['email'].lower().strip(),
            full_name=data['full_name'].strip(),
            phone=data['phone'].strip(),
            blood_group=data['blood_group'],
            city=data['city'].strip().title(),
            location=data['location'].strip(),
            age=int(data['age']),
            weight=float(data['weight']),
            gender=data['gender'].strip().lower(),
            is_available=data.get('is_available', True)
        )
        user.set_password(data['password'])

        db.session.add(user)
        db.session.commit()

        token = create_access_token(identity=user.id)

        return jsonify({
            'message': 'Registration successful',
            'token': token,
            'user': user.to_dict(include_private=True)
        }), 201

    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500


@app.route('/api/auth/login', methods=['POST'])
def login():
    data = request.get_json()
    email = data.get('email', '').lower().strip()
    password = data.get('password', '')

    if not email or not password:
        return jsonify({'error': 'Email and password are required'}), 400

    user = User.query.filter_by(email=email).first()

    if not user or not user.check_password(password):
        return jsonify({'error': 'Invalid email or password'}), 401

    token = create_access_token(identity=user.id)

    return jsonify({
        'message': 'Login successful',
        'token': token,
        'user': user.to_dict(include_private=True)
    }), 200


@app.route('/api/auth/me', methods=['GET'])
@jwt_required()
def get_current_user():
    user_id = get_jwt_identity()
    user = User.query.get_or_404(user_id)
    return jsonify({'user': user.to_dict(include_private=True)}), 200


@app.route('/api/auth/profile', methods=['PUT'])
@jwt_required()
def update_profile():
    user_id = get_jwt_identity()
    user = User.query.get_or_404(user_id)
    data = request.get_json()

    allowed_fields = ['full_name', 'phone', 'city', 'location', 'age', 'weight', 'is_available']

    for field in allowed_fields:
        if field in data:
            if field in ['age']:
                setattr(user, field, int(data[field]))
            elif field in ['weight']:
                setattr(user, field, float(data[field]))
            elif field == 'is_available':
                setattr(user, field, bool(data[field]))
            else:
                setattr(user, field, data[field].strip() if isinstance(data[field], str) else data[field])

    user.updated_at = datetime.utcnow()
    db.session.commit()

    return jsonify({
        'message': 'Profile updated successfully',
        'user': user.to_dict(include_private=True)
    }), 200


# ==========================
# DONOR SEARCH ENDPOINTS
# ==========================

@app.route('/api/donors/search', methods=['GET'])
@jwt_required()
def search_donors():
    blood_group = request.args.get('blood_group', '')
    city = request.args.get('city', '')
    location = request.args.get('location', '')

    query = User.query

    if blood_group:
        query = query.filter(User.blood_group == blood_group.upper())
    if city:
        query = query.filter(User.city.ilike(f'%{city}%'))
    if location:
        query = query.filter(User.location.ilike(f'%{location}%'))

    # Only show available donors
    query = query.filter(User.is_available == True)

    donors = query.all()

    # Filter eligible donors in Python (since eligibility depends on last_donation_date)
    eligible_donors = [d for d in donors if d.is_eligible]

    return jsonify({
        'count': len(eligible_donors),
        'donors': [d.to_dict() for d in eligible_donors]
    }), 200


@app.route('/api/donors/<int:donor_id>', methods=['GET'])
@jwt_required()
def get_donor(donor_id):
    donor = User.query.get_or_404(donor_id)
    return jsonify({'donor': donor.to_dict()}), 200


# ==========================
# DONATION ENDPOINTS
# ==========================

from PIL import Image, ImageDraw, ImageFont

def generate_certificate(donation):
    """Generate a blood donation certificate as PNG image"""

    # Create certificate image (A4 landscape ratio)
    width, height = 1200, 850
    img = Image.new('RGB', (width, height), color='#fefefe')
    draw = ImageDraw.Draw(img)

    # Try to load fonts, fallback to default
    try:
        font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 48)
        font_subtitle = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 28)
        font_body = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 22)
        font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)
    except:
        font_title = ImageFont.load_default()
        font_subtitle = font_title
        font_body = font_title
        font_small = font_title

    # Border
    border_color = '#c62828'
    draw.rectangle([20, 20, width-20, height-20], outline=border_color, width=8)
    draw.rectangle([35, 35, width-35, height-35], outline='#b71c1c', width=2)

    # Header
    draw.text((width//2, 80), "BLOOD DONATION CERTIFICATE", fill='#c62828', font=font_title, anchor='mm')
    draw.text((width//2, 140), "Certificate of Appreciation", fill='#555', font=font_subtitle, anchor='mm')

    # Decorative line
    draw.line([(100, 180), (width-100, 180)], fill='#c62828', width=3)

    # Body text
    donor = donation.donor
    y_pos = 240
    line_height = 45

    texts = [
        ("This is to certify that", '#333', font_subtitle),
        (f"{donor.full_name}", '#c62828', font_title),
        ("has generously donated blood and saved lives.", '#333', font_subtitle),
        ("", '#333', font_body),
        (f"Blood Group: {donor.blood_group}", '#333', font_body),
        (f"Date of Donation: {donation.donation_date.strftime('%B %d, %Y')}", '#333', font_body),
        (f"Units Donated: {donation.units}", '#333', font_body),
        (f"Hospital: {donation.hospital_name}, {donation.hospital_city}", '#333', font_body),
        ("", '#333', font_body),
        (f"Certificate ID: {donation.certificate_id}", '#666', font_small),
    ]

    for text, color, font in texts:
        if text:
            draw.text((width//2, y_pos), text, fill=color, font=font, anchor='mm')
        y_pos += line_height

    # QR Code
    qr = qrcode.QRCode(version=1, box_size=4, border=2)
    qr.add_data(f"CERT-ID:{donation.certificate_id}\nDonor:{donor.full_name}\nDate:{donation.donation_date}")
    qr.make(fit=True)
    qr_img = qr.make_image(fill_color='#c62828', back_color='white')
    qr_img = qr_img.resize((150, 150))
    img.paste(qr_img, (width - 220, height - 220))

    # Footer
    draw.text((width//2, height - 80), "Thank you for being a lifesaver!", fill='#c62828', font=font_subtitle, anchor='mm')
    draw.text((width//2, height - 45), f"Issued on: {datetime.now().strftime('%B %d, %Y')}", fill='#666', font=font_small, anchor='mm')

    # Save to buffer
    buffer = BytesIO()
    img.save(buffer, format='PNG', quality=95)
    buffer.seek(0)

    return buffer


@app.route('/api/donations', methods=['POST'])
@jwt_required()
def record_donation():
    user_id = get_jwt_identity()
    user = User.query.get_or_404(user_id)
    data = request.get_json()

    required = ['hospital_name', 'hospital_city']
    for field in required:
        if not data.get(field):
            return jsonify({'error': f'{field} is required'}), 400

    # Check eligibility
    if not user.is_eligible:
        return jsonify({
            'error': 'You are not eligible to donate yet.',
            'days_until_eligible': user.days_until_eligible,
            'next_eligible_date': (user.last_donation_date + timedelta(days=56)).isoformat()
        }), 403

    try:
        donation = Donation(
            donor_id=user_id,
            donation_date=datetime.strptime(data.get('donation_date', datetime.now().strftime('%Y-%m-%d')), '%Y-%m-%d').date(),
            units=float(data.get('units', 1.0)),
            hospital_name=data['hospital_name'].strip(),
            hospital_city=data['hospital_city'].strip().title(),
            certificate_id=f"BD-{uuid.uuid4().hex[:12].upper()}",
            notes=data.get('notes', '')
        )

        db.session.add(donation)

        # Update donor stats
        user.last_donation_date = donation.donation_date
        user.total_donations += 1

        db.session.commit()

        return jsonify({
            'message': 'Donation recorded successfully',
            'donation': donation.to_dict()
        }), 201

    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500


@app.route('/api/donations/my', methods=['GET'])
@jwt_required()
def get_my_donations():
    user_id = get_jwt_identity()
    donations = Donation.query.filter_by(donor_id=user_id).order_by(Donation.donation_date.desc()).all()
    return jsonify({
        'count': len(donations),
        'donations': [d.to_dict() for d in donations]
    }), 200


@app.route('/api/donations/<int:donation_id>/certificate', methods=['GET'])
@jwt_required()
def get_certificate(donation_id):
    user_id = get_jwt_identity()
    donation = Donation.query.get_or_404(donation_id)

    # Only donor or admin can view certificate
    if donation.donor_id != user_id:
        return jsonify({'error': 'Unauthorized'}), 403

    cert_buffer = generate_certificate(donation)

    return send_file(
        cert_buffer,
        mimetype='image/png',
        as_attachment=True,
        download_name=f"blood_donation_certificate_{donation.certificate_id}.png"
    )


# ==========================
# EVENT / NEWSLETTER ENDPOINTS
# ==========================

@app.route('/api/events', methods=['POST'])
@jwt_required()
def create_event():
    user_id = get_jwt_identity()
    data = request.get_json()

    required = ['title', 'description', 'city', 'venue', 'event_date', 'organizer_name', 'organizer_contact']
    for field in required:
        if not data.get(field):
            return jsonify({'error': f'{field} is required'}), 400

    try:
        event = Event(
            title=data['title'].strip(),
            description=data['description'].strip(),
            city=data['city'].strip().title(),
            venue=data['venue'].strip(),
            event_date=datetime.fromisoformat(data['event_date'].replace('Z', '+00:00')),
            organizer_name=data['organizer_name'].strip(),
            organizer_contact=data['organizer_contact'].strip(),
            created_by=user_id
        )

        db.session.add(event)
        db.session.commit()

        return jsonify({
            'message': 'Event created successfully',
            'event': event.to_dict()
        }), 201

    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500


@app.route('/api/events', methods=['GET'])
@jwt_required()
def get_events():
    city = request.args.get('city', '')

    query = Event.query.filter(Event.event_date >= datetime.utcnow())

    if city:
        query = query.filter(Event.city.ilike(f'%{city}%'))

    events = query.order_by(Event.event_date.asc()).all()

    return jsonify({
        'count': len(events),
        'events': [e.to_dict() for e in events]
    }), 200


@app.route('/api/events/subscribe', methods=['POST'])
@jwt_required()
def subscribe_city():
    user_id = get_jwt_identity()
    data = request.get_json()
    city = data.get('city', '').strip().title()

    if not city:
        return jsonify({'error': 'City is required'}), 400

    existing = EventSubscription.query.filter_by(user_id=user_id, city=city).first()
    if existing:
        return jsonify({'message': f'Already subscribed to {city}'}), 200

    try:
        sub = EventSubscription(user_id=user_id, city=city)
        db.session.add(sub)
        db.session.commit()

        return jsonify({'message': f'Successfully subscribed to {city} events'}), 201

    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500


@app.route('/api/events/unsubscribe/<string:city>', methods=['DELETE'])
@jwt_required()
def unsubscribe_city(city):
    user_id = get_jwt_identity()
    city = city.strip().title()

    sub = EventSubscription.query.filter_by(user_id=user_id, city=city).first()
    if not sub:
        return jsonify({'error': 'Subscription not found'}), 404

    db.session.delete(sub)
    db.session.commit()

    return jsonify({'message': f'Unsubscribed from {city} events'}), 200


@app.route('/api/events/my-subscriptions', methods=['GET'])
@jwt_required()
def get_my_subscriptions():
    user_id = get_jwt_identity()
    subs = EventSubscription.query.filter_by(user_id=user_id).all()

    # Get events for subscribed cities
    cities = [s.city for s in subs]
    events = []
    if cities:
        events = Event.query.filter(
            Event.city.in_(cities),
            Event.event_date >= datetime.utcnow()
        ).order_by(Event.event_date.asc()).all()

    return jsonify({
        'subscriptions': [{'city': s.city, 'subscribed_at': s.subscribed_at.isoformat()} for s in subs],
        'upcoming_events': [e.to_dict() for e in events]
    }), 200


# ==========================
# DASHBOARD / STATS
# ==========================

@app.route('/api/stats', methods=['GET'])
def get_stats():
    total_donors = User.query.count()
    total_donations = Donation.query.count()
    eligible_donors = sum(1 for u in User.query.all() if u.is_eligible)
    upcoming_events = Event.query.filter(Event.event_date >= datetime.utcnow()).count()

    # Blood group distribution
    bg_counts = {}
    for bg in ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']:
        count = User.query.filter_by(blood_group=bg).count()
        if count > 0:
            bg_counts[bg] = count

    return jsonify({
        'total_donors': total_donors,
        'total_donations': total_donations,
        'eligible_donors': eligible_donors,
        'upcoming_events': upcoming_events,
        'blood_group_distribution': bg_counts
    }), 200


# ==========================
# FRONTEND ROUTE
# ==========================

@app.route('/')
def index():
    return render_template('index.html')


@app.route('/api/health', methods=['GET'])
def health_check():
    return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()}), 200


# ==========================
# ERROR HANDLERS
# ==========================

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Resource not found'}), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Internal server error'}), 500


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)
