# -*- coding: utf-8 -*-
"""
Кабинет Директора — веб-версия (Flask + SQLite).

Готово к мультипользователю, вход пока один (см. auth_config.json / таблица users).
  • сессия НЕ постоянная (session cookie) — закрытие браузера разлогинивает;
  • закрытие вкладки тоже разлогинивает (страж на sessionStorage, см. base.html).
"""
import os
import uuid
import secrets
from functools import wraps
from datetime import date, timedelta

from flask import (
    Flask, render_template, request, redirect, url_for,
    session, flash, jsonify
)
from werkzeug.security import check_password_hash

import db

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads")
ALLOWED_IMG = {"png", "jpg", "jpeg", "gif", "webp"}
ALLOWED_DOC = {"pdf", "doc", "docx", "xls", "xlsx", "txt", "csv", "rtf", "odt", "ods"}

app = Flask(__name__)

# --- Секретный ключ (постоянный между перезапусками) ---
SECRET_KEY_PATH = os.path.join(BASE_DIR, ".secret_key")


def _get_or_create_secret_key():
    env_key = os.environ.get("SECRET_KEY")
    if env_key:
        return env_key
    if os.path.exists(SECRET_KEY_PATH):
        with open(SECRET_KEY_PATH, "r", encoding="utf-8") as f:
            return f.read().strip()
    new_key = secrets.token_hex(32)
    with open(SECRET_KEY_PATH, "w", encoding="utf-8") as f:
        f.write(new_key)
    return new_key


app.secret_key = _get_or_create_secret_key()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.config["SESSION_COOKIE_SECURE"] = os.environ.get("COOKIE_SECURE", "0") == "1"
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024  # 16 МБ на загрузку

db.init_db()
os.makedirs(UPLOAD_DIR, exist_ok=True)


# ---------------- Аутентификация ----------------

def login_required(view):
    @wraps(view)
    def wrapped(*args, **kwargs):
        if not session.get("user_id"):
            if request.path.startswith("/api/"):
                return jsonify({"error": "unauthorized"}), 401
            return redirect(url_for("login"))
        return view(*args, **kwargs)
    return wrapped


def current_user(conn):
    return db.get_user(conn, session["user_id"])


@app.route("/")
def index():
    if session.get("user_id"):
        return redirect(url_for("dashboard"))
    return redirect(url_for("login"))


@app.route("/login", methods=["GET", "POST"])
def login():
    if session.get("user_id"):
        return redirect(url_for("dashboard"))
    if request.method == "POST":
        email = (request.form.get("email") or "").strip().lower()
        password = request.form.get("password") or ""
        conn = db.get_db()
        try:
            user = db.get_user_by_email(conn, email)
        finally:
            conn.close()
        if user and check_password_hash(user["password_hash"], password):
            session.clear()
            session["user_id"] = user["id"]
            session["seed"] = True
            return redirect(url_for("dashboard"))
        flash("Неверная почта или пароль")
        return redirect(url_for("login"))
    return render_template("login.html")


@app.route("/logout")
def logout():
    session.clear()
    return redirect(url_for("login"))


@app.route("/dashboard")
@login_required
def dashboard():
    seed = session.pop("seed", False)
    return redirect(url_for("tasks_page", **({"seed": 1} if seed else {})))


# ---------------- Страница задач ----------------

@app.route("/tasks")
@login_required
def tasks_page():
    seed = bool(request.args.get("seed"))
    return render_template("tasks.html", seed=seed)


@app.route("/payments")
@login_required
def payments_page():
    return render_template("payments.html", seed=False)


# ---------------- Сериализация ----------------

def _task_dict(conn, t, me_id):
    sender = db.get_user(conn, t["sender_id"]) if t["sender_id"] else None
    owner = db.get_user(conn, t["owner_id"]) if t["owner_id"] else None
    return {
        "id": t["id"],
        "owner_id": t["owner_id"],
        "owner_name": owner["name"] if owner else "",
        "sender_id": t["sender_id"],
        "sender_name": sender["name"] if sender else "",
        "description": t["description"],
        "priority": t["priority"],
        "has_deadline": bool(t["has_deadline"]),
        "deadline": t["deadline"],
        "notes": t["notes"],
        "is_personal": bool(t["is_personal"]),
        "is_group": bool(t["is_group"]),
        "placement": t["placement"],
        "plan_date": t["plan_date"],
        "plan_tab_id": t["plan_tab_id"],
        "completed": bool(t["completed"]),
        "created_at": t["created_at"],
        "incoming": bool(t["sender_id"] and t["sender_id"] != me_id),
        "participants": db.task_participant_ids(conn, t["id"]) if t["is_group"] else [],
        "images": db.task_images(conn, t["id"]),
    }


@app.route("/api/tasks")
@login_required
def api_tasks():
    try:
        offset = int(request.args.get("offset", 0))
    except ValueError:
        offset = 0
    offset = max(0, offset)
    window = 30

    conn = db.get_db()
    try:
        me = current_user(conn)
        db.rollover_calendar_tasks(conn, me["id"])
        tabs = [dict(r) for r in db.list_plan_tabs(conn, me["id"])]
        users = [dict(r) for r in db.list_users(conn)]
        rows = db.list_tasks_for_user(conn, me["id"])
        tasks = [_task_dict(conn, t, me["id"]) for t in rows]
        deleg_rows = db.list_delegated_tasks(conn, me["id"])
        delegated = [_task_dict(conn, t, me["id"]) for t in deleg_rows]
    finally:
        conn.close()

    today = date.today()
    days = []
    for i in range(window):
        d = today + timedelta(days=offset + i)
        days.append({
            "date": d.isoformat(),
            "day": d.strftime("%d.%m"),
            "weekday": ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"][d.weekday()],
            "is_today": d == today,
            "is_weekend": d.weekday() >= 5,
        })

    start = today + timedelta(days=offset)
    end = start + timedelta(days=window - 1)
    return jsonify({
        "me": {"id": me["id"], "name": me["name"], "email": me["email"]},
        "users": users,
        "tabs": tabs,
        "priorities": db.PRIORITIES,
        "tasks": tasks,
        "delegated": delegated,
        "window": {
            "offset": offset,
            "size": window,
            "label": (f"Ближайшие {window} дней" if offset == 0
                      else f"+{offset}…+{offset + window} дн."),
            "range": f"{start.strftime('%d.%m')} – {end.strftime('%d.%m')}",
        },
        "days": days,
        "today": today.isoformat(),
    })


@app.route("/api/tasks", methods=["POST"])
@login_required
def api_create_task():
    d = request.get_json(force=True) or {}
    desc = (d.get("description") or "").strip()
    if not desc:
        return jsonify({"error": "empty"}), 400
    priority = d.get("priority") if d.get("priority") in db.PRIORITIES else "ВАЖНО"
    has_deadline = bool(d.get("has_deadline"))
    deadline = (d.get("deadline") or None) if has_deadline else None
    is_group = bool(d.get("is_group"))
    is_personal = bool(d.get("is_personal")) and not is_group
    if not is_group and not is_personal:
        is_personal = True

    conn = db.get_db()
    try:
        me = current_user(conn)
        recipient_id = d.get("recipient_id") or me["id"]
        if not db.get_user(conn, recipient_id):
            recipient_id = me["id"]
        task_id = db.create_task(
            conn,
            owner_id=recipient_id,
            sender_id=me["id"],
            description=desc,
            priority=priority,
            has_deadline=has_deadline,
            deadline=deadline,
            notes=d.get("notes") or "",
            is_personal=is_personal,
            is_group=is_group,
            participant_ids=d.get("participant_ids") or [],
        )
    finally:
        conn.close()
    return jsonify({"ok": True, "id": task_id})


@app.route("/api/tasks/<int:task_id>/update", methods=["POST"])
@login_required
def api_update_task(task_id):
    d = request.get_json(force=True) or {}
    conn = db.get_db()
    try:
        me = current_user(conn)
        t = db.get_task(conn, task_id)
        if not t or not _can_edit(conn, t, me["id"]):
            return jsonify({"error": "forbidden"}), 403
        fields = {}
        if "description" in d:
            fields["description"] = (d["description"] or "").strip()
        if d.get("priority") in db.PRIORITIES:
            fields["priority"] = d["priority"]
        if "notes" in d:
            fields["notes"] = d["notes"] or ""
        if "has_deadline" in d:
            hd = bool(d["has_deadline"])
            fields["has_deadline"] = 1 if hd else 0
            fields["deadline"] = (d.get("deadline") or None) if hd else None
        if "is_group" in d or "is_personal" in d:
            is_group = bool(d.get("is_group"))
            fields["is_group"] = 1 if is_group else 0
            fields["is_personal"] = 0 if is_group else 1
            if is_group:
                db.set_participants(conn, task_id, t["owner_id"],
                                    d.get("participant_ids") or [])
        elif "participant_ids" in d and t["is_group"]:
            db.set_participants(conn, task_id, t["owner_id"], d["participant_ids"])
        if fields:
            db.update_task(conn, task_id, **fields)
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/tasks/<int:task_id>/move", methods=["POST"])
@login_required
def api_move_task(task_id):
    d = request.get_json(force=True) or {}
    placement = d.get("placement")
    if placement not in (db.PLACEMENT_INBOX, db.PLACEMENT_TAB, db.PLACEMENT_CALENDAR):
        return jsonify({"error": "bad placement"}), 400
    conn = db.get_db()
    try:
        me = current_user(conn)
        t = db.get_task(conn, task_id)
        if not t or not _can_edit(conn, t, me["id"]):
            return jsonify({"error": "forbidden"}), 403
        plan_date = d.get("plan_date") if placement == db.PLACEMENT_CALENDAR else None
        plan_tab_id = d.get("plan_tab_id") if placement == db.PLACEMENT_TAB else None
        db.move_task(conn, task_id, placement=placement,
                     plan_date=plan_date, plan_tab_id=plan_tab_id)
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/tasks/<int:task_id>/toggle", methods=["POST"])
@login_required
def api_toggle_task(task_id):
    conn = db.get_db()
    try:
        me = current_user(conn)
        t = db.get_task(conn, task_id)
        if not t or not _can_edit(conn, t, me["id"]):
            return jsonify({"error": "forbidden"}), 403
        db.toggle_completed(conn, task_id)
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/tasks/<int:task_id>/delete", methods=["POST"])
@login_required
def api_delete_task(task_id):
    conn = db.get_db()
    try:
        me = current_user(conn)
        t = db.get_task(conn, task_id)
        if not t:
            return jsonify({"error": "not found"}), 404
        # Удалять может владелец ИЛИ отправитель (отмена поручения).
        if t["owner_id"] != me["id"] and t["sender_id"] != me["id"]:
            return jsonify({"error": "forbidden"}), 403
        db.delete_task(conn, task_id)
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/tasks/<int:task_id>/image", methods=["POST"])
@login_required
def api_upload_image(task_id):
    if "image" not in request.files:
        return jsonify({"error": "no file"}), 400
    f = request.files["image"]
    ext = f.filename.rsplit(".", 1)[-1].lower() if "." in f.filename else ""
    if ext not in ALLOWED_IMG:
        return jsonify({"error": "bad type"}), 400
    conn = db.get_db()
    try:
        me = current_user(conn)
        t = db.get_task(conn, task_id)
        if not t or not _can_edit(conn, t, me["id"]):
            return jsonify({"error": "forbidden"}), 403
        tdir = os.path.join(UPLOAD_DIR, str(task_id))
        os.makedirs(tdir, exist_ok=True)
        fname = f"{uuid.uuid4().hex}.{ext}"
        f.save(os.path.join(tdir, fname))
        rel = f"{task_id}/{fname}"
        db.add_task_image(conn, task_id, rel)
    finally:
        conn.close()
    return jsonify({"ok": True, "url": url_for("static", filename=f"uploads/{rel}")})


# ---------------- Вкладки ----------------

@app.route("/api/tabs", methods=["POST"])
@login_required
def api_add_tab():
    d = request.get_json(force=True) or {}
    conn = db.get_db()
    try:
        me = current_user(conn)
        tab_id = db.add_plan_tab(conn, me["id"], d.get("name") or "Вкладка")
    finally:
        conn.close()
    return jsonify({"ok": True, "id": tab_id})


@app.route("/api/tabs/<int:tab_id>/rename", methods=["POST"])
@login_required
def api_rename_tab(tab_id):
    d = request.get_json(force=True) or {}
    conn = db.get_db()
    try:
        me = current_user(conn)
        db.rename_plan_tab(conn, me["id"], tab_id, d.get("name") or "")
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/tabs/<int:tab_id>/delete", methods=["POST"])
@login_required
def api_delete_tab(tab_id):
    conn = db.get_db()
    try:
        me = current_user(conn)
        db.delete_plan_tab(conn, me["id"], tab_id)
    finally:
        conn.close()
    return jsonify({"ok": True})


# ---------------- Оплаты ----------------

def _payment_dict(conn, p):
    return {
        "id": p["id"],
        "payee": p["payee"],
        "amount": p["amount"],
        "deadline": p["deadline"],
        "comment": p["comment"],
        "status": p["status"],
        "created_by": p["created_by"],
        "created_by_name": p["created_by_name"] if "created_by_name" in p.keys() else "",
        "paid_by": p["paid_by"],
        "paid_by_name": p["paid_by_name"] if "paid_by_name" in p.keys() else "",
        "created_at": p["created_at"],
        "paid_at": p["paid_at"],
        "files": [
            {**f, "url": url_for("static", filename=f"uploads/payments/{f['filename']}")}
            for f in db.payment_files(conn, p["id"])
        ],
    }


def _save_payment_files(conn, payment_id, files):
    if not files:
        return
    pdir = os.path.join(UPLOAD_DIR, "payments", str(payment_id))
    os.makedirs(pdir, exist_ok=True)
    for f in files:
        if not f or not f.filename:
            continue
        ext = f.filename.rsplit(".", 1)[-1].lower() if "." in f.filename else ""
        if ext not in ALLOWED_IMG and ext not in ALLOWED_DOC:
            continue
        fname = f"{uuid.uuid4().hex}.{ext}"
        f.save(os.path.join(pdir, fname))
        db.add_payment_file(conn, payment_id, f"{payment_id}/{fname}",
                            f.filename, ext in ALLOWED_IMG)


@app.route("/api/payments")
@login_required
def api_payments():
    conn = db.get_db()
    try:
        me = current_user(conn)
        queue = [_payment_dict(conn, p) for p in db.list_payments(conn, "queue")]
        paid = [_payment_dict(conn, p) for p in db.list_payments(conn, "paid")]
        stats = db.payment_stats(conn)
        users = [dict(r) for r in db.list_users(conn)]
    finally:
        conn.close()
    return jsonify({
        "me": {"id": me["id"], "name": me["name"]},
        "users": users,
        "queue": queue,
        "paid": paid,
        "stats": stats,
        "today": date.today().isoformat(),
    })


@app.route("/api/payments", methods=["POST"])
@login_required
def api_create_payment():
    # multipart/form-data: поля + необязательные файлы (документ/фото)
    payee = (request.form.get("payee") or "").strip()
    comment = (request.form.get("comment") or "").strip()
    deadline = (request.form.get("deadline") or "").strip() or None
    try:
        amount = float((request.form.get("amount") or "0").replace(",", "."))
    except ValueError:
        amount = 0.0
    if not payee:
        return jsonify({"error": "Не указано, кому оплата"}), 400
    conn = db.get_db()
    try:
        me = current_user(conn)
        payment_id = db.create_payment(
            conn, payee=payee, amount=amount, deadline=deadline,
            comment=comment, created_by=me["id"],
        )
        _save_payment_files(conn, payment_id, request.files.getlist("files"))
    finally:
        conn.close()
    return jsonify({"ok": True, "id": payment_id})


@app.route("/api/payments/<int:payment_id>/pay", methods=["POST"])
@login_required
def api_pay_payment(payment_id):
    conn = db.get_db()
    try:
        me = current_user(conn)
        if not db.get_payment(conn, payment_id):
            return jsonify({"error": "not found"}), 404
        db.mark_payment_paid(conn, payment_id, me["id"])
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/payments/<int:payment_id>/unpay", methods=["POST"])
@login_required
def api_unpay_payment(payment_id):
    conn = db.get_db()
    try:
        if not db.get_payment(conn, payment_id):
            return jsonify({"error": "not found"}), 404
        db.unmark_payment_paid(conn, payment_id)
    finally:
        conn.close()
    return jsonify({"ok": True})


@app.route("/api/payments/<int:payment_id>/delete", methods=["POST"])
@login_required
def api_delete_payment(payment_id):
    conn = db.get_db()
    try:
        if not db.get_payment(conn, payment_id):
            return jsonify({"error": "not found"}), 404
        db.delete_payment(conn, payment_id)
    finally:
        conn.close()
    # удаляем прикреплённые файлы с диска
    import shutil
    pdir = os.path.join(UPLOAD_DIR, "payments", str(payment_id))
    if os.path.isdir(pdir):
        shutil.rmtree(pdir, ignore_errors=True)
    return jsonify({"ok": True})


def _can_edit(conn, t, uid):
    if t["owner_id"] == uid:
        return True
    if t["is_group"] and uid in db.task_participant_ids(conn, t["id"]):
        return True
    return False


if __name__ == "__main__":
    app.run(debug=True, port=5000)
