from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, login_required, logout_user, current_user, UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from markupsafe import Markup
import markdown as md
from flask import request, flash, abort
import os
import re
import socket

app = Flask(__name__)
# Ładujemy konfigurację z pliku config.py
app.config.from_object('config.Config')

db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

# MODELE

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)  # Flaga głównego administratora

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

class Zbiorka(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nazwa = db.Column(db.String(100), nullable=False)
    opis = db.Column(db.Text, nullable=False)
    numer_konta = db.Column(db.String(50), nullable=False)
    numer_telefonu_blik = db.Column(db.String(50), nullable=False)
    cel = db.Column(db.Float, nullable=False, default=0.0)
    stan = db.Column(db.Float, default=0.0)
    ukryta = db.Column(db.Boolean, default=False)
    ukryj_kwote = db.Column(db.Boolean, default=False)
    wplaty = db.relationship('Wplata', backref='zbiorka', lazy=True, order_by='Wplata.data.desc()')
    zrealizowana = db.Column(db.Boolean, default=False)

class Wplata(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    zbiorka_id = db.Column(db.Integer, db.ForeignKey('zbiorka.id'), nullable=False)
    kwota = db.Column(db.Float, nullable=False)
    data = db.Column(db.DateTime, default=datetime.utcnow)
    opis = db.Column(db.Text, nullable=True)  # Opis wpłaty

class GlobalSettings(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    numer_konta = db.Column(db.String(50), nullable=False)
    numer_telefonu_blik = db.Column(db.String(50), nullable=False)
    allowed_login_hosts = db.Column(db.Text, nullable=True)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

def is_allowed_ip(remote_ip, allowed_hosts_str):
    # Jeśli istnieje plik awaryjny, zawsze zezwalamy na dostęp
    if os.path.exists("emergency_access.txt"):
        return True

    # Rozdzielamy wpisy – mogą być oddzielone przecinkami lub znakami nowej linii
    allowed_hosts = re.split(r'[\n,]+', allowed_hosts_str.strip())
    allowed_ips = set()
    for host in allowed_hosts:
        host = host.strip()
        if not host:
            continue
        try:
            # Rozwiązywanie nazwy domeny do adresu IP.
            resolved_ip = socket.gethostbyname(host)
            allowed_ips.add(resolved_ip)
        except Exception:
            # Jeśli rozwiązywanie nazwy nie powiedzie się, pomijamy ten wpis.
            continue
    return remote_ip in allowed_ips

# Dodaj filtr Markdown – pozwala na zagnieżdżanie linków i obrazków w opisie
@app.template_filter('markdown')
def markdown_filter(text):
    return Markup(md.markdown(text))

# TRASY PUBLICZNE

@app.route('/')
def index():
    zbiorki = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).all()
    return render_template('index.html', zbiorki=zbiorki)

@app.route('/zbiorki_zrealizowane')
def zbiorki_zrealizowane():
    zbiorki = Zbiorka.query.filter_by(zrealizowana=True).all()
    return render_template('index.html', zbiorki=zbiorki)

@app.errorhandler(404)
def page_not_found(e):
    return redirect(url_for('index'))

@app.route('/zbiorka/<int:zbiorka_id>')
def zbiorka(zbiorka_id):
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    # Jeżeli zbiórka jest ukryta i użytkownik nie jest administratorem, zwróć 404
    if zb.ukryta and (not current_user.is_authenticated or not current_user.is_admin):
        abort(404)
    return render_template('zbiorka.html', zbiorka=zb)

def get_real_ip():
    # Cloudflare
    if "CF-Connecting-IP" in request.headers:
        return request.headers.get("CF-Connecting-IP")
    # Nginx proxy (Nginx Proxy Manager / standard reverse proxy)
    elif "X-Real-IP" in request.headers:
        return request.headers.get("X-Real-IP")
    elif "X-Forwarded-For" in request.headers:
        forwarded_for = request.headers.get("X-Forwarded-For").split(",")
        return forwarded_for[0].strip()
    # Fallback
    return request.remote_addr

# TRASY LOGOWANIA I REJESTRACJI

@app.route('/login', methods=['GET', 'POST'])
def login():
    # Pobierz ustawienia globalne, w tym dozwolone hosty
    settings = GlobalSettings.query.first()
    allowed_hosts_str = ""
    if settings and settings.allowed_login_hosts:
        allowed_hosts_str = settings.allowed_login_hosts

    # Sprawdzenie, czy adres IP klienta jest dozwolony
    client_ip = get_real_ip()
    if not is_allowed_ip(client_ip, allowed_hosts_str):
        flash('Dostęp do endpointu /login jest zablokowany dla Twojego adresu IP', 'danger')
        return redirect(url_for('index'))

    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            flash('Zalogowano pomyślnie', 'success')
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('admin_dashboard'))
        else:
            flash('Nieprawidłowe dane logowania', 'danger')
    return render_template('login.html')


@app.route('/logout')
@login_required
def logout():
    logout_user()
    flash('Wylogowano', 'success')
    return redirect(url_for('login'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if not app.config.get('ALLOW_REGISTRATION', False):
        flash('Rejestracja została wyłączona przez administratora', 'danger')
        return redirect(url_for('login'))
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if User.query.filter_by(username=username).first():
            flash('Użytkownik już istnieje', 'danger')
            return redirect(url_for('register'))
        new_user = User(username=username)
        new_user.set_password(password)
        db.session.add(new_user)
        db.session.commit()
        flash('Konto utworzone, możesz się zalogować', 'success')
        return redirect(url_for('login'))
    return render_template('register.html')

# PANEL ADMINISTRACYJNY

@app.route('/admin')
@login_required
def admin_dashboard():
    if not current_user.is_admin:
        flash('Brak uprawnień do panelu administracyjnego', 'danger')
        return redirect(url_for('index'))
    active_zbiorki = Zbiorka.query.filter_by(zrealizowana=False).all()
    completed_zbiorki = Zbiorka.query.filter_by(zrealizowana=True).all()
    return render_template('admin/dashboard.html', active_zbiorki=active_zbiorki, 
                                                   completed_zbiorki=completed_zbiorki)

@app.route('/admin/zbiorka/dodaj', methods=['GET', 'POST'])
@login_required
def dodaj_zbiorka():
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
        
    global_settings = GlobalSettings.query.first()  # Pobieramy globalne ustawienia
    
    if request.method == 'POST':
        nazwa = request.form['nazwa']
        opis = request.form['opis']
        # Pozyskujemy numer konta i telefon z formularza (mogą być nadpisane ręcznie)
        numer_konta = request.form['numer_konta']
        numer_telefonu_blik = request.form['numer_telefonu_blik']
        cel = float(request.form['cel'])
        ukryj_kwote = 'ukryj_kwote' in request.form
        
        nowa_zbiorka = Zbiorka(
            nazwa=nazwa, 
            opis=opis, 
            numer_konta=numer_konta,
            numer_telefonu_blik=numer_telefonu_blik,
            cel=cel,
            ukryj_kwote=ukryj_kwote
        )
        db.session.add(nowa_zbiorka)
        db.session.commit()
        flash('Zbiórka została dodana', 'success')
        return redirect(url_for('admin_dashboard'))

    return render_template('admin/add_zbiorka.html', global_settings=global_settings)

@app.route('/admin/zbiorka/edytuj/<int:zbiorka_id>', methods=['GET', 'POST'])
@login_required
def edytuj_zbiorka(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    global_settings = GlobalSettings.query.first()  # Pobieramy globalne ustawienia
    if request.method == 'POST':
        zb.nazwa = request.form['nazwa']
        zb.opis = request.form['opis']
        zb.numer_konta = request.form['numer_konta']
        zb.numer_telefonu_blik = request.form['numer_telefonu_blik']
        try:
            zb.cel = float(request.form['cel'])
        except ValueError:
            flash('Podano nieprawidłową wartość dla celu zbiórki', 'danger')
            return render_template('admin/edit_zbiorka.html', zbiorka=zb, global_settings=global_settings)
        zb.ukryj_kwote = 'ukryj_kwote' in request.form
        db.session.commit()
        flash('Zbiórka została zaktualizowana', 'success')
        return redirect(url_for('admin_dashboard'))
    return render_template('admin/edit_zbiorka.html', zbiorka=zb, global_settings=global_settings)

# TRASA DODAWANIA WPŁATY Z OPISEM
# TRASA DODAWANIA WPŁATY W PANELU ADMINA
@app.route('/admin/zbiorka/<int:zbiorka_id>/wplata/dodaj', methods=['GET', 'POST'])
@login_required
def admin_dodaj_wplate(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    if request.method == 'POST':
        kwota = float(request.form['kwota'])
        opis = request.form.get('opis', '')
        nowa_wplata = Wplata(zbiorka_id=zb.id, kwota=kwota, opis=opis)
        zb.stan += kwota  # Aktualizacja stanu zbiórki
        db.session.add(nowa_wplata)
        db.session.commit()
        flash('Wpłata została dodana', 'success')
        return redirect(url_for('admin_dashboard'))
    return render_template('admin/add_wplata.html', zbiorka=zb)

@app.route('/admin/zbiorka/usun/<int:zbiorka_id>', methods=['POST'])
@login_required
def usun_zbiorka(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    db.session.delete(zb)
    db.session.commit()
    flash('Zbiórka została usunięta', 'success')
    return redirect(url_for('admin_dashboard'))

@app.route('/admin/zbiorka/edytuj_stan/<int:zbiorka_id>', methods=['GET', 'POST'])
@login_required
def edytuj_stan(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    if request.method == 'POST':
        try:
            nowy_stan = float(request.form['stan'])
        except ValueError:
            flash('Nieprawidłowa wartość kwoty', 'danger')
            return redirect(url_for('edytuj_stan', zbiorka_id=zbiorka_id))
        zb.stan = nowy_stan
        db.session.commit()
        flash('Stan zbiórki został zaktualizowany', 'success')
        return redirect(url_for('admin_dashboard'))
    return render_template('admin/edytuj_stan.html', zbiorka=zb)

@app.route('/admin/zbiorka/toggle_visibility/<int:zbiorka_id>', methods=['POST'])
@login_required
def toggle_visibility(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    zb.ukryta = not zb.ukryta
    db.session.commit()
    flash('Zbiórka została ' + ('ukryta' if zb.ukryta else 'przywrócona'), 'success')
    return redirect(url_for('admin_dashboard'))

def create_admin_account():
    admin = User.query.filter_by(is_admin=True).first()
    if not admin:
        main_admin = User(username=app.config['MAIN_ADMIN_USERNAME'], is_admin=True)
        main_admin.set_password(app.config['MAIN_ADMIN_PASSWORD'])
        db.session.add(main_admin)
        db.session.commit()

@app.after_request
def add_security_headers(response):
    if app.config.get("BLOCK_BOTS", False):
        cache_control = app.config.get("CACHE_CONTROL_HEADER")
        if cache_control:
            response.headers["Cache-Control"] = cache_control
            # Jeśli Cache-Control jest ustawiony, usuwamy Pragma
            response.headers.pop("Pragma", None)
        else:
            response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
            response.headers["Pragma"] = app.config.get("PRAGMA_HEADER", "no-cache")
        response.headers["X-Robots-Tag"] = app.config.get("ROBOTS_TAG", "noindex, nofollow, nosnippet, noarchive")
    return response

@app.route('/admin/settings', methods=['GET', 'POST'])
@login_required
def admin_settings():
    if not current_user.is_admin:
         flash('Brak uprawnień do panelu administracyjnego', 'danger')
         return redirect(url_for('index'))
         
    settings = GlobalSettings.query.first()
    if request.method == 'POST':
         numer_konta = request.form.get('numer_konta')
         numer_telefonu_blik = request.form.get('numer_telefonu_blik')
         allowed_login_hosts = request.form.get('allowed_login_hosts')
         
         if settings is None:
             settings = GlobalSettings(
                 numer_konta=numer_konta,
                 numer_telefonu_blik=numer_telefonu_blik,
                 allowed_login_hosts=allowed_login_hosts
             )
             db.session.add(settings)
         else:
             settings.numer_konta = numer_konta
             settings.numer_telefonu_blik = numer_telefonu_blik
             settings.allowed_login_hosts = allowed_login_hosts
             
         db.session.commit()
         flash('Ustawienia globalne zostały zaktualizowane', 'success')
         return redirect(url_for('admin_dashboard'))
         
    return render_template('admin/settings.html', settings=settings)

@app.route('/admin/zbiorka/oznacz/<int:zbiorka_id>', methods=['POST'])
@login_required
def oznacz_zbiorka(zbiorka_id):
    if not current_user.is_admin:
        flash('Brak uprawnień do wykonania tej operacji', 'danger')
        return redirect(url_for('index'))
    zb = Zbiorka.query.get_or_404(zbiorka_id)
    zb.zrealizowana = True
    db.session.commit()
    flash('Zbiórka została oznaczona jako zrealizowana', 'success')
    return redirect(url_for('admin_dashboard'))

@app.route('/robots.txt')
def robots():
    if app.config.get("BLOCK_BOTS", False):
        # Instrukcje dla robotów – blokujemy indeksowanie całej witryny
        robots_txt = "User-agent: *\nDisallow: /"
    else:
        # Jeśli blokowanie botów wyłączone, można zwrócić pusty plik lub inne ustawienia
        robots_txt = "User-agent: *\nAllow: /"
    return robots_txt, 200, {'Content-Type': 'text/plain'}

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        # Tworzenie konta głównego admina, jeśli nie istnieje
        if not User.query.filter_by(is_admin=True).first():
            main_admin = User(username=app.config['MAIN_ADMIN_USERNAME'], is_admin=True)
            main_admin.set_password(app.config['MAIN_ADMIN_PASSWORD'])
            db.session.add(main_admin)
            db.session.commit()
    app.run(debug=True)