import hashlib
import time
import json
from flask import Flask, jsonify, request, render_template, redirect, url_for, flash
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, DecimalField
from wtforms.validators import DataRequired
import requests
from werkzeug.security import generate_password_hash, check_password_hash
import random
from threading import Timer

app = Flask(__name__)
app.secret_key = 'your_secret_key'
login_manager = LoginManager()
login_manager.init_app(app)

# Локальная база данных
users = {}
staking = {}  # Хранение долей пользователей
user_resources = {}  # Хранение ресурсов для каждого пользователя
integrity_reports = []  # Список отчетов о целостности


class User(UserMixin):
    def __init__(self, username, password):
        self.username = username
        self.set_password(password)

    def set_password(self, password):
        self.password = generate_password_hash(password)

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


@login_manager.user_loader
def load_user(username):
    return users.get(username)


class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Register')


class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')


class StakeForm(FlaskForm):
    amount = DecimalField('Amount to Stake', validators=[DataRequired()])
    submit = SubmitField('Stake')


class ResourceForm(FlaskForm):
    resource_name = StringField('Resource Name', validators=[DataRequired()])
    resource_data = StringField('Resource Data (text)', validators=[DataRequired()])
    submit = SubmitField('Add Resource')


class Block:
    def __init__(self, index, previous_hash, timestamp, data, validator):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.data = data
        self.validator = validator
        self.hash = self.hash_block()

    def hash_block(self):
        block_string = json.dumps(self.data, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    def to_dict(self):
        return {
            "index": self.index,
            "previous_hash": self.previous_hash,
            "timestamp": self.timestamp,
            "data": self.data,
            "validator": self.validator,
            "hash": self.hash
        }


class Blockchain:
    def __init__(self):
        self.chain = []
        self.current_data = []
        self.nodes = set()
        self.create_block(previous_hash='1', validator="System")  # Создание первого блока
        self.start_integrity_check()  # Запуск регулярной проверки целостности

    def create_block(self, previous_hash, validator):
        block = Block(
            index=len(self.chain) + 1,
            previous_hash=previous_hash,
            timestamp=int(time.time()),
            data=self.current_data,
            validator=validator
        )
        self.current_data = []
        self.chain.append(block)
        return block

    def add_resource(self, resource_name, resource_data):
        resource_hash = hashlib.sha256(resource_data.encode()).hexdigest()
        self.current_data.append({"name": resource_name, "data": resource_hash})

    def get_chain(self):
        return [block.to_dict() for block in self.chain]

    def is_chain_valid(self):
        for i in range(1, len(self.chain)):
            current = self.chain[i]
            previous = self.chain[i - 1]

            # Проверка на правильность хешей
            if current.previous_hash != previous.hash:
                return False
            if current.hash != current.hash_block():
                return False
        return True

    def add_node(self, address):
        self.nodes.add(address)

    def replace_chain(self):
        longest_chain = None
        max_length = len(self.chain)

        for node in self.nodes:
            response = requests.get(f'http://{node}/chain')

            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']

                if length > max_length and self.is_valid_chain(chain):
                    max_length = length
                    longest_chain = chain

        if longest_chain:
            self.chain = longest_chain
            return True
        return False

    @staticmethod
    def is_valid_chain(chain):
        for i in range(1, len(chain)):
            current = chain[i]
            previous = chain[i - 1]

            if current['previous_hash'] != previous['hash']:
                return False
            if current['hash'] != Blockchain.hash_block_from_dict(previous):
                return False
        return True

    @staticmethod
    def hash_block_from_dict(block):
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    def check_integrity(self):
        integrity_issues = []
        for username, resources in user_resources.items():
            for resource in resources:
                # Отправляем запрос на обновление данных ресурса (например, если хеш был изменен)
                current_hash = hashlib.sha256(resource['data'].encode()).hexdigest()
                if current_hash != resource['data']:  # Неверное значение: основное тело ресурса изменено
                    integrity_issues.append(f'Ресурс {resource["name"]} пользователя {username} изменён!')

        if integrity_issues:
            self.generate_report(integrity_issues)

    def generate_report(self, issues):
        timestamp = int(time.time())
        report = {
            "timestamp": timestamp,
            "user": current_user.username,
            "issues": issues
        }
        integrity_reports.append(report)
        flash('Отчет о целостности сгенерирован. Проверьте проблемы!', 'warning')

    def start_integrity_check(self):
        """ Запускает периодическую проверку целостности ресурсов. """
        Timer(60.0, self.integrity_check).start()  # Запуск проверки каждый 60 секунд


blockchain = Blockchain()


@app.route('/')
@login_required
def home():
    user_resources_list = user_resources.get(current_user.username, [])
    return render_template('index.html', chain=blockchain.get_chain(), length=len(blockchain.chain),
                           resources=user_resources_list)


@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        if username not in users:
            users[username] = User(username, password)
            staking[username] = 0.0  # Начальный staking
            user_resources[username] = []  # Начальный список ресурсов
            flash('Вы успешно зарегистрированы!', 'success')
            return redirect(url_for('login'))
        else:
            flash('Имя пользователя уже существует. Выберите другое.', 'danger')
    return render_template('register.html', form=form)


@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        user = load_user(username)
        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for('home'))
        else:
            flash('Неверное имя пользователя или пароль!', 'danger')
    return render_template('login.html', form=form)


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


@app.route('/stake', methods=['GET', 'POST'])
@login_required
def stake():
    form = StakeForm()
    if form.validate_on_submit():
        amount = form.amount.data
        if amount <= 0:
            flash('Сумма должна быть положительной!', 'danger')
        else:
            staking[current_user.username] += amount  # Увеличиваем долю
            flash(f'Вы успешно добавили {amount} к вашей доле!', 'success')
    return render_template('stake.html', form=form)


@app.route('/add_resource', methods=['GET', 'POST'])
@login_required
def add_resource():
    form = ResourceForm()
    if form.validate_on_submit():
        resource_name = form.resource_name.data
        resource_data = form.resource_data.data
        # Добавляем ресурс в блокчейн
        blockchain.add_resource(resource_name, resource_data)
        user_resources[current_user.username].append({"name": resource_name, "data": resource_data})
        flash('Ресурс успешно добавлен!', 'success')
        return redirect(url_for('home'))
    return render_template('add_resource.html', form=form)


@app.route('/add_data', methods=['POST'])
@login_required
def add_data():
    if not is_validator(current_user.username):
        return jsonify({"error": "Вы не являетесь валидатором!"}), 403

    data = request.get_json()
    blockchain.add_data(data['data'])
    response = blockchain.create_block(previous_hash=blockchain.chain[-1].hash, validator=current_user.username)
    return jsonify(response.to_dict()), 201


def is_validator(username):
    total_stake = sum(staking.values())
    user_stake = staking.get(username, 0)
    if total_stake == 0:  # Если нет валидаторов
        return False
    return random.random() < (user_stake / total_stake)


@app.route('/chain', methods=['GET'])
@login_required
def get_chain():
    response = {
        "chain": blockchain.get_chain(),
        "length": len(blockchain.chain),
    }
    return jsonify(response), 200


@app.route('/validate', methods=['GET'])
@login_required
def validate_chain():
    is_valid = blockchain.is_chain_valid()
    return jsonify({"valid": is_valid}), 200


@app.route('/add_node', methods=['POST'])
@login_required
def add_node():
    node_address = request.get_json()["node"]
    blockchain.add_node(node_address)
    return jsonify({"message": f"Node {node_address} добавлен."}), 201


@app.route('/replace_chain', methods=['GET'])
@login_required
def replace_chain():
    replaced = blockchain.replace_chain()
    if replaced:
        return jsonify({"message": "Цепь заменена на самую длинную."}), 200
    return jsonify({"message": "Цепь является самой длинной."}), 200


@app.route('/integrity_reports', methods=['GET'])
@login_required
def integrity_reports_view():
    return render_template('integrity_reports.html', reports=integrity_reports)


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