import secrets
import uuid
from datetime import datetime, timezone

from flask import request, jsonify

from app.api import api_bp
from app.api.auth import token_required
from app.extensions import db
from app.models.calendar import Calendar, CalendarEvent, CalendarShare
from app.models.user import User


def _get_calendar_or_err(cal_id, user, need_write=False):
    cal = db.session.get(Calendar, cal_id)
    if not cal:
        return None, (jsonify({'error': 'Kalender nicht gefunden'}), 404)
    if cal.owner_id == user.id:
        return cal, None
    share = CalendarShare.query.filter_by(
        calendar_id=cal_id, shared_with_id=user.id
    ).first()
    if not share:
        return None, (jsonify({'error': 'Zugriff verweigert'}), 403)
    if need_write and share.permission != 'readwrite':
        return None, (jsonify({'error': 'Schreibzugriff verweigert'}), 403)
    return cal, None


# --- Calendars ---

@api_bp.route('/calendars', methods=['GET'])
@token_required
def list_calendars():
    user = request.current_user
    own = Calendar.query.filter_by(owner_id=user.id).all()
    shared_ids = [s.calendar_id for s in
                  CalendarShare.query.filter_by(shared_with_id=user.id).all()]
    shared = Calendar.query.filter(Calendar.id.in_(shared_ids)).all() if shared_ids else []

    result = []
    for c in own:
        d = c.to_dict()
        d['permission'] = 'owner'
        result.append(d)
    for c in shared:
        d = c.to_dict()
        share = CalendarShare.query.filter_by(
            calendar_id=c.id, shared_with_id=user.id
        ).first()
        d['permission'] = share.permission if share else 'read'
        d['owner_name'] = c.owner.username
        result.append(d)

    return jsonify(result), 200


@api_bp.route('/calendars', methods=['POST'])
@token_required
def create_calendar():
    user = request.current_user
    data = request.get_json()
    name = data.get('name', '').strip()
    if not name:
        return jsonify({'error': 'Name erforderlich'}), 400

    cal = Calendar(
        owner_id=user.id,
        name=name,
        color=data.get('color', '#3788d8'),
        description=data.get('description', ''),
    )
    db.session.add(cal)
    db.session.commit()
    return jsonify(cal.to_dict()), 201


@api_bp.route('/calendars/<int:cal_id>', methods=['PUT'])
@token_required
def update_calendar(cal_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nicht gefunden oder keine Berechtigung'}), 404

    data = request.get_json()
    if 'name' in data:
        cal.name = data['name'].strip()
    if 'color' in data:
        cal.color = data['color']
    if 'description' in data:
        cal.description = data['description']

    db.session.commit()
    return jsonify(cal.to_dict()), 200


@api_bp.route('/calendars/<int:cal_id>', methods=['DELETE'])
@token_required
def delete_calendar(cal_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nicht gefunden oder keine Berechtigung'}), 404

    db.session.delete(cal)
    db.session.commit()
    return jsonify({'message': 'Kalender geloescht'}), 200


# --- Events ---

@api_bp.route('/calendars/<int:cal_id>/events', methods=['GET'])
@token_required
def list_events(cal_id):
    user = request.current_user
    cal, err = _get_calendar_or_err(cal_id, user)
    if err:
        return err

    start = request.args.get('start')
    end = request.args.get('end')

    query = CalendarEvent.query.filter_by(calendar_id=cal_id)
    if start:
        try:
            start_dt = datetime.fromisoformat(start)
            query = query.filter(CalendarEvent.dtend >= start_dt)
        except ValueError:
            pass
    if end:
        try:
            end_dt = datetime.fromisoformat(end)
            query = query.filter(CalendarEvent.dtstart <= end_dt)
        except ValueError:
            pass

    events = query.order_by(CalendarEvent.dtstart).all()
    return jsonify([e.to_dict() for e in events]), 200


@api_bp.route('/calendars/<int:cal_id>/events', methods=['POST'])
@token_required
def create_event(cal_id):
    user = request.current_user
    cal, err = _get_calendar_or_err(cal_id, user, need_write=True)
    if err:
        return err

    data = request.get_json()
    summary = data.get('summary', '').strip()
    if not summary:
        return jsonify({'error': 'Zusammenfassung erforderlich'}), 400

    dtstart = data.get('dtstart')
    dtend = data.get('dtend')
    all_day = data.get('all_day', False)

    if not dtstart:
        return jsonify({'error': 'Startdatum erforderlich'}), 400

    try:
        dtstart_dt = datetime.fromisoformat(dtstart)
        dtend_dt = datetime.fromisoformat(dtend) if dtend else dtstart_dt
    except ValueError:
        return jsonify({'error': 'Ungueltiges Datumsformat'}), 400

    event_uid = str(uuid.uuid4())

    # Build simple iCal data
    ical_data = _build_ical(event_uid, summary, dtstart_dt, dtend_dt, all_day,
                            data.get('description', ''), data.get('location', ''),
                            data.get('recurrence_rule', ''))

    event = CalendarEvent(
        calendar_id=cal_id,
        uid=event_uid,
        ical_data=ical_data,
        summary=summary,
        dtstart=dtstart_dt,
        dtend=dtend_dt,
        all_day=all_day,
        recurrence_rule=data.get('recurrence_rule'),
    )
    db.session.add(event)
    db.session.commit()
    return jsonify(event.to_dict()), 201


@api_bp.route('/events/<int:event_id>', methods=['PUT'])
@token_required
def update_event(event_id):
    user = request.current_user
    event = db.session.get(CalendarEvent, event_id)
    if not event:
        return jsonify({'error': 'Event nicht gefunden'}), 404

    cal, err = _get_calendar_or_err(event.calendar_id, user, need_write=True)
    if err:
        return err

    data = request.get_json()
    if 'summary' in data:
        event.summary = data['summary'].strip()
    if 'dtstart' in data:
        event.dtstart = datetime.fromisoformat(data['dtstart'])
    if 'dtend' in data:
        event.dtend = datetime.fromisoformat(data['dtend'])
    if 'all_day' in data:
        event.all_day = data['all_day']
    if 'recurrence_rule' in data:
        event.recurrence_rule = data['recurrence_rule']
    if 'calendar_id' in data:
        new_cal, cerr = _get_calendar_or_err(data['calendar_id'], user, need_write=True)
        if cerr:
            return cerr
        event.calendar_id = data['calendar_id']

    event.ical_data = _build_ical(
        event.uid, event.summary, event.dtstart, event.dtend,
        event.all_day, data.get('description', ''), data.get('location', ''),
        event.recurrence_rule or ''
    )
    event.updated_at = datetime.now(timezone.utc)
    db.session.commit()
    return jsonify(event.to_dict()), 200


@api_bp.route('/events/<int:event_id>', methods=['DELETE'])
@token_required
def delete_event(event_id):
    user = request.current_user
    event = db.session.get(CalendarEvent, event_id)
    if not event:
        return jsonify({'error': 'Event nicht gefunden'}), 404

    cal, err = _get_calendar_or_err(event.calendar_id, user, need_write=True)
    if err:
        return err

    db.session.delete(event)
    db.session.commit()
    return jsonify({'message': 'Event geloescht'}), 200


# --- Calendar sharing ---

@api_bp.route('/calendars/<int:cal_id>/share', methods=['POST'])
@token_required
def share_calendar(cal_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nur der Eigentuemer kann teilen'}), 403

    data = request.get_json()
    username = data.get('username', '').strip()
    permission = data.get('permission', 'read')

    if permission not in ('read', 'readwrite'):
        return jsonify({'error': 'Ungueltige Berechtigung'}), 400

    target = User.query.filter_by(username=username).first()
    if not target:
        return jsonify({'error': 'Benutzer nicht gefunden'}), 404
    if target.id == user.id:
        return jsonify({'error': 'Kann nicht mit sich selbst teilen'}), 400

    existing = CalendarShare.query.filter_by(
        calendar_id=cal_id, shared_with_id=target.id
    ).first()
    is_new = not existing
    if existing:
        existing.permission = permission
    else:
        share = CalendarShare(
            calendar_id=cal_id, shared_with_id=target.id, permission=permission
        )
        db.session.add(share)

    db.session.commit()

    if is_new:
        try:
            from app.services.system_mail import notify_calendar_shared
            notify_calendar_shared(cal.name, user.username, target, permission)
        except Exception:
            pass

    return jsonify({'message': f'Kalender mit {username} geteilt'}), 200


@api_bp.route('/calendars/<int:cal_id>/shares', methods=['GET'])
@token_required
def list_calendar_shares(cal_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nicht gefunden'}), 404

    shares = CalendarShare.query.filter_by(calendar_id=cal_id).all()
    return jsonify([{
        'id': s.id,
        'user_id': s.shared_with_id,
        'username': s.shared_with.username,
        'permission': s.permission,
    } for s in shares]), 200


@api_bp.route('/calendars/<int:cal_id>/shares/<int:share_id>', methods=['DELETE'])
@token_required
def remove_calendar_share(cal_id, share_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nicht gefunden'}), 404

    share = db.session.get(CalendarShare, share_id)
    if not share or share.calendar_id != cal_id:
        return jsonify({'error': 'Freigabe nicht gefunden'}), 404

    db.session.delete(share)
    db.session.commit()
    return jsonify({'message': 'Freigabe entfernt'}), 200


# --- iCal Export ---

@api_bp.route('/calendars/<int:cal_id>/ical-link', methods=['POST'])
@token_required
def generate_ical_link(cal_id):
    user = request.current_user
    cal = db.session.get(Calendar, cal_id)
    if not cal or cal.owner_id != user.id:
        return jsonify({'error': 'Nicht gefunden'}), 404

    cal.ical_token = secrets.token_urlsafe(32)
    db.session.commit()
    return jsonify({
        'ical_url': f'/ical/{cal.ical_token}',
        'token': cal.ical_token,
    }), 200


def ical_export(token):
    cal = Calendar.query.filter_by(ical_token=token).first()
    if not cal:
        return jsonify({'error': 'Nicht gefunden'}), 404

    events = CalendarEvent.query.filter_by(calendar_id=cal.id).all()

    lines = [
        'BEGIN:VCALENDAR',
        'VERSION:2.0',
        'PRODID:-//Mini-Cloud//DE',
        f'X-WR-CALNAME:{cal.name}',
    ]
    for e in events:
        if e.ical_data:
            # Extract VEVENT from stored ical_data
            lines.append(e.ical_data)
        else:
            lines.append(_build_vevent(e.uid, e.summary, e.dtstart, e.dtend, e.all_day))
    lines.append('END:VCALENDAR')

    from flask import Response
    return Response(
        '\r\n'.join(lines),
        mimetype='text/calendar',
        headers={'Content-Disposition': f'attachment; filename="{cal.name}.ics"'},
    )


# --- Helpers ---

def _format_dt(dt, all_day=False):
    if all_day:
        return dt.strftime('%Y%m%d')
    return dt.strftime('%Y%m%dT%H%M%SZ')


def _build_vevent(uid, summary, dtstart, dtend, all_day, description='', location='', rrule=''):
    lines = [
        'BEGIN:VEVENT',
        f'UID:{uid}',
    ]
    if all_day:
        lines.append(f'DTSTART;VALUE=DATE:{_format_dt(dtstart, True)}')
        lines.append(f'DTEND;VALUE=DATE:{_format_dt(dtend, True)}')
    else:
        lines.append(f'DTSTART:{_format_dt(dtstart)}')
        lines.append(f'DTEND:{_format_dt(dtend)}')
    lines.append(f'SUMMARY:{summary}')
    if description:
        lines.append(f'DESCRIPTION:{description}')
    if location:
        lines.append(f'LOCATION:{location}')
    if rrule:
        lines.append(f'RRULE:{rrule}')
    lines.append(f'DTSTAMP:{datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")}')
    lines.append('END:VEVENT')
    return '\r\n'.join(lines)


def _build_ical(uid, summary, dtstart, dtend, all_day, description='', location='', rrule=''):
    return _build_vevent(uid, summary, dtstart, dtend, all_day, description, location, rrule)
