import re
import random
from functools import wraps
from datetime import datetime
from flask import Flask, render_template, request, session, redirect, url_for, flash, abort
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from faker import Faker

fake = Faker('ru_RU')

app = Flask(__name__)
application = app
app.secret_key = '1234567890secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth'
login_manager.login_message = 'Для доступа к данной странице необходимо пройти процедуру аутентификации.'
login_manager.login_message_category = 'warning'


# ===== Кастомные типы данных =====

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text, nullable=True)
    users = db.relationship('User', back_populates='role')

    def __repr__(self):
        return f'<Role {self.name}>'


class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    login = db.Column(db.String(100), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    last_name = db.Column(db.String(100), nullable=True)
    first_name = db.Column(db.String(100), nullable=False)
    middle_name = db.Column(db.String(100), nullable=True)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), nullable=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    role = db.relationship('Role', back_populates='users')

    def get_full_name(self):
        parts = [self.last_name, self.first_name, self.middle_name]
        return ' '.join(p for p in parts if p)

    def check_password(self, password):
        return bcrypt.check_password_hash(self.password_hash, password)

    def set_password(self, password):
        self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')

    def __repr__(self):
        return f'<User {self.login}>'


class VisitLog(db.Model):
    __tablename__ = 'visit_logs'
    id = db.Column(db.Integer, primary_key=True)
    path = db.Column(db.String(100))
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user = db.relationship('User', backref='visit_logs')

    def __repr__(self):
        return f'<VisitLog {self.path}>'


@login_manager.user_loader
def load_user(user_id):
    return User.query.filter_by(id=int(user_id)).first()


def init_db():
    db.create_all()
    if not Role.query.first():
        roles = [
            Role(name='Администратор', description='Полный доступ к системе'),
            Role(name='Пользователь', description='Базовый доступ к системе'),
        ]
        db.session.add_all(roles)
        db.session.commit()
    if not User.query.first():
        admin_role = Role.query.filter_by(name='Администратор').first()
        admin = User(
            login='admin',
            first_name='Администратор',
            last_name='Системный',
            middle_name=None,
            role_id=admin_role.id if admin_role else None
        )
        admin.set_password('Admin123!')
        db.session.add(admin)
        db.session.commit()


# ===== Декоратор проверки прав =====

def check_rights(action):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.is_authenticated:
                flash('У вас недостаточно прав для доступа к данной странице.', 'warning')
                return redirect(url_for('index'))

            role_name = current_user.role.name if current_user.role else None
            allowed = False

            if role_name == 'Администратор':
                allowed = True
            elif role_name == 'Пользователь':
                if action in ('edit_users', 'view_profile'):
                    user_id = kwargs.get('user_id')
                    allowed = (user_id == current_user.id)
                elif action == 'view_logs':
                    allowed = True

            if not allowed:
                flash('У вас недостаточно прав для доступа к данной странице.', 'warning')
                return redirect(url_for('index'))

            return f(*args, **kwargs)
        return decorated_function
    return decorator


# ===== Логирование посещений =====

@app.before_request
def log_visit():
    if not request.path.startswith('/static/'):
        try:
            log_entry = VisitLog(
                path=request.path,
                user_id=current_user.id if current_user.is_authenticated else None
            )
            db.session.add(log_entry)
            db.session.commit()
        except Exception:
            db.session.rollback()


# ===== Валидация =====
def validate_login(login):
    errors = []
    if not login:
        errors.append('Поле не может быть пустым.')
    elif len(login) < 5:
        errors.append('Логин должен содержать не менее 5 символов.')
    elif not re.match(r'^[a-zA-Z0-9]+$', login):
        errors.append('Логин должен состоять только из латинских букв и цифр.')
    return errors


def validate_password(password):
    errors = []
    if not password:
        errors.append('Поле не может быть пустым.')
        return errors
    if len(password) < 8:
        errors.append('Пароль должен содержать не менее 8 символов.')
    if len(password) > 128:
        errors.append('Пароль не должен превышать 128 символов.')
    if not re.search(r'[A-Z]', password):
        errors.append('Пароль должен содержать хотя бы одну заглавную букву.')
    if not re.search(r'[a-z]', password):
        errors.append('Пароль должен содержать хотя бы одну строчную букву.')
    if not re.search(r'\d', password):
        errors.append('Пароль должен содержать хотя бы одну цифру.')
    if ' ' in password:
        errors.append('Пароль не должен содержать пробелы.')
    allowed_special = set('~!?@#$%^&*_-+()[]{}><\\/|"\'.,;:')
    for ch in password:
        if not (ch.isalpha() or ch.isdigit() or ch in allowed_special):
            errors.append('Пароль содержит недопустимые символы.')
            break
    return errors


# ===== Предыдущие функции (Lab 1-3) =====
images_ids = ['Pic1', 'Pic2', 'Pic3', 'Pic4', 'Pic5']


def generate_comments(replies=True):
    comments = []
    for i in range(random.randint(1, 3)):
        comment = {'author': fake.name(), 'text': fake.text()}
        if replies:
            comment['replies'] = generate_comments(replies=False)
        comments.append(comment)
    return comments


def generate_post(i):
    return {
        'title': fake.company(),
        'text': fake.paragraph(nb_sentences=100),
        'author': fake.name(),
        'date': fake.date_time_between(start_date='-2y', end_date='now'),
        'image_id': f'{images_ids[i]}.jpg',
        'comments': generate_comments()
    }


posts_list = sorted([generate_post(i) for i in range(5)], key=lambda p: p['date'], reverse=True)


@app.route('/posts')
def posts():
    return render_template('posts.html', title='Посты', posts=posts_list)


@app.route('/posts/<int:index>')
def post(index):
    p = posts_list[index]
    return render_template('post.html', title=p['title'], post=p)


@app.route('/about')
def about():
    return render_template('about.html', title='Об авторе')


@app.route('/visits')
def visits():
    if 'visits' in session:
        session['visits'] = session.get('visits') + 1
    else:
        session['visits'] = 1
    return render_template('visits.html', title='Счётчик посещений', visits=session['visits'])


@app.route('/auth', methods=['GET', 'POST'])
def auth():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    if request.method == 'POST':
        login_input = request.form.get('login', '')
        password_input = request.form.get('password', '')
        remember = request.form.get('remember') == 'on'
        user = User.query.filter_by(login=login_input).first()
        if user and user.check_password(password_input):
            login_user(user, remember=remember)
            flash('Вы успешно вошли в систему!', 'success')
            next_page = request.args.get('next')
            return redirect(next_page or url_for('index'))
        flash('Неверный логин или пароль.', 'error')
    return render_template('login.html', title='Вход')


@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))


@app.route('/secret')
@login_required
def secret():
    return render_template('secret.html', title='Секретная страница')


@app.route('/request-data', methods=['GET', 'POST'])
def request_data():
    form_data = None
    if request.method == 'POST':
        form_data = {
            'login': request.form.get('login', ''),
            'password': request.form.get('password', '')
        }
    return render_template(
        'request_data.html',
        title='Данные запроса',
        url_params=request.args,
        headers=request.headers,
        cookies=request.cookies,
        form_data=form_data
    )


def validate_phone(phone):
    allowed = set('0123456789 ()-.+')
    for ch in phone:
        if ch not in allowed:
            return None, 'Недопустимый ввод. В номере телефона встречаются недопустимые символы.'
    digits = re.sub(r'\D', '', phone)
    stripped = phone.strip()
    if stripped.startswith('+7') or stripped.startswith('8'):
        if len(digits) != 11:
            return None, 'Недопустимый ввод. Неверное количество цифр.'
    else:
        if len(digits) != 10:
            return None, 'Недопустимый ввод. Неверное количество цифр.'
    if len(digits) == 11:
        digits = digits[1:]
    formatted = f'8-{digits[0:3]}-{digits[3:6]}-{digits[6:8]}-{digits[8:10]}'
    return formatted, None


@app.route('/phone', methods=['GET', 'POST'])
def phone():
    error = None
    formatted_phone = None
    phone_value = ''
    if request.method == 'POST':
        phone_value = request.form.get('phone', '')
        formatted_phone, error = validate_phone(phone_value)
    return render_template(
        'phone.html',
        title='Проверка номера телефона',
        error=error,
        formatted_phone=formatted_phone,
        phone_value=phone_value
    )


# ===== Обновлённый функционал (Lab 4-5) =====
@app.route('/')
def index():
    return render_template('index.html', title='Задание — Лабораторная работа №5')


@app.route('/users')
def users_list():
    users = User.query.all()
    return render_template('users.html', title='Управление пользователями', users=users)


@app.route('/users/<int:user_id>')
@check_rights('view_profile')
def user_view(user_id):
    user = User.query.filter_by(id=user_id).first_or_404()
    return render_template('user_view.html', title=f'Пользователь: {user.login}', user=user)


@app.route('/users/create', methods=['GET', 'POST'])
@check_rights('create_users')
def user_create():
    roles = Role.query.all()
    errors = {}
    form_data = {}

    if request.method == 'POST':
        login = request.form.get('login', '').strip()
        password = request.form.get('password', '')
        last_name = request.form.get('last_name', '').strip()
        first_name = request.form.get('first_name', '').strip()
        middle_name = request.form.get('middle_name', '').strip()
        role_id = request.form.get('role_id') or None

        form_data = {
            'login': login,
            'last_name': last_name,
            'first_name': first_name,
            'middle_name': middle_name,
            'role_id': role_id,
        }

        login_errors = validate_login(login)
        if login_errors:
            errors['login'] = login_errors
        elif User.query.filter_by(login=login).first():
            errors['login'] = ['Пользователь с таким логином уже существует.']

        password_errors = validate_password(password)
        if password_errors:
            errors['password'] = password_errors

        if not last_name:
            errors['last_name'] = ['Поле не может быть пустым.']

        if not first_name:
            errors['first_name'] = ['Поле не может быть пустым.']

        if not errors:
            try:
                user = User(
                    login=login,
                    first_name=first_name,
                    last_name=last_name or None,
                    middle_name=middle_name or None,
                    role_id=int(role_id) if role_id else None
                )
                user.set_password(password)
                db.session.add(user)
                db.session.commit()
                flash('Пользователь успешно создан!', 'success')
                return redirect(url_for('users_list'))
            except Exception as e:
                db.session.rollback()
                flash(f'Ошибка при сохранении: {e}', 'error')

    return render_template('user_create.html', title='Создать пользователя',
                           roles=roles, errors=errors, form_data=form_data)


@app.route('/users/<int:user_id>/edit', methods=['GET', 'POST'])
@check_rights('edit_users')
def user_edit(user_id):
    user = User.query.filter_by(id=user_id).first_or_404()
    roles = Role.query.all()
    errors = {}
    is_admin = current_user.role and current_user.role.name == 'Администратор'

    if request.method == 'POST':
        last_name = request.form.get('last_name', '').strip()
        first_name = request.form.get('first_name', '').strip()
        middle_name = request.form.get('middle_name', '').strip()
        role_id = request.form.get('role_id') or None

        form_data = {
            'last_name': last_name,
            'first_name': first_name,
            'middle_name': middle_name,
            'role_id': role_id,
        }

        if not last_name:
            errors['last_name'] = ['Поле не может быть пустым.']

        if not first_name:
            errors['first_name'] = ['Поле не может быть пустым.']

        if not errors:
            try:
                user.last_name = last_name or None
                user.first_name = first_name
                user.middle_name = middle_name or None
                if is_admin:
                    user.role_id = int(role_id) if role_id else None
                db.session.commit()
                flash('Пользователь успешно обновлён!', 'success')
                return redirect(url_for('users_list'))
            except Exception as e:
                db.session.rollback()
                flash(f'Ошибка при сохранении: {e}', 'error')
    else:
        form_data = {
            'last_name': user.last_name or '',
            'first_name': user.first_name or '',
            'middle_name': user.middle_name or '',
            'role_id': user.role_id,
        }

    return render_template('user_edit.html', title='Редактировать пользователя',
                           user=user, roles=roles, errors=errors, form_data=form_data,
                           disable_role=not is_admin)


@app.route('/users/<int:user_id>/delete', methods=['POST'])
@check_rights('delete_users')
def user_delete(user_id):
    user = User.query.filter_by(id=user_id).first_or_404()
    try:
        db.session.delete(user)
        db.session.commit()
        flash('Пользователь успешно удалён!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Ошибка при удалении: {e}', 'error')
    return redirect(url_for('users_list'))


@app.route('/change-password', methods=['GET', 'POST'])
@login_required
def change_password():
    errors = {}
    if request.method == 'POST':
        old_password = request.form.get('old_password', '')
        new_password = request.form.get('new_password', '')
        confirm_password = request.form.get('confirm_password', '')

        if not current_user.check_password(old_password):
            errors['old_password'] = ['Неверный текущий пароль.']

        new_password_errors = validate_password(new_password)
        if new_password_errors:
            errors['new_password'] = new_password_errors

        if new_password and not new_password_errors and confirm_password != new_password:
            errors['confirm_password'] = ['Пароли не совпадают.']

        if not errors:
            try:
                current_user.set_password(new_password)
                db.session.commit()
                flash('Пароль успешно изменён!', 'success')
                return redirect(url_for('index'))
            except Exception as e:
                db.session.rollback()
                flash(f'Ошибка при изменении пароля: {e}', 'error')

    return render_template('change_password.html', title='Смена пароля', errors=errors)


# ===== Регистрация Blueprint журнала посещений =====
from visit_logs import visit_logs_bp
app.register_blueprint(visit_logs_bp)

# ===== Инициализация БД =====
with app.app_context():
    init_db()


if __name__ == '__main__':
    app.run(debug=True)
