"""
app.py — Flask wrap of kanban.py for cPanel/Passenger deployment.

Imports kanban.py as a library (re-uses load_data, save_data, calendar helpers,
calculate_next_fire, sync_projects). Re-implements the HTTP route layer in Flask
and adds HTTP Basic Auth.

Environment variables:
  KANBAN_USER          — Basic Auth username (default: "Nick")
  KANBAN_PASS          — Basic Auth password (required; no default)
  KANBAN_DATA_FILE     — override data file path (default: kanban_data.json next to this file)
  KANBAN_PROJECTS_DIR  — override folder-scan directory. Set to a non-existent path to
                         disable folder-based project sync (live server and local test
                         both want this; folder scan only makes sense on Nick's Mac).
  PORT                 — local dev port (default: 8090; ignored under Passenger)

Run locally:
  KANBAN_PASS=@Lytha09 KANBAN_DATA_FILE=kanban_data_test.json python3 app.py

Production (cPanel Python Selector):
  passenger_wsgi.py imports `app` from this module.
"""

import os
import sys
import uuid
from functools import wraps
from datetime import datetime

from flask import Flask, request, Response, jsonify, send_from_directory

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPT_DIR)
import kanban  # noqa: E402  — imported after sys.path adjustment

# Override data file path if env var set
_data_override = os.environ.get("KANBAN_DATA_FILE")
if _data_override:
    if not os.path.isabs(_data_override):
        _data_override = os.path.join(SCRIPT_DIR, _data_override)
    kanban.DATA_FILE = _data_override

# Override folder-scan directory. Live server and local test both want this disabled
# so sync_projects returns empty (the scan only makes sense on Nick's Mac, not on a
# remote server). Set to a non-existent path to make scan_project_folders() a no-op.
_projects_dir_override = os.environ.get("KANBAN_PROJECTS_DIR")
if _projects_dir_override:
    kanban.PROJECTS_DIR = _projects_dir_override

STATIC_DIR = os.path.join(SCRIPT_DIR, "static")

AUTH_USER = os.environ.get("KANBAN_USER", "Nick")
AUTH_PASS = os.environ.get("KANBAN_PASS")  # No default; fail closed.

app = Flask(__name__, static_folder=None)


# ─── Auth ──────────────────────────────────────

def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not AUTH_PASS:
            return Response("Auth not configured (KANBAN_PASS unset)", 500)
        auth = request.authorization
        if not auth or auth.username != AUTH_USER or auth.password != AUTH_PASS:
            return Response(
                "Authentication required",
                401,
                {"WWW-Authenticate": 'Basic realm="Kanban"'}
            )
        return f(*args, **kwargs)
    return decorated


# ─── Static files ──────────────────────────────

@app.route("/")
@app.route("/index.html")
@require_auth
def index():
    return send_from_directory(STATIC_DIR, "index.html")

@app.route("/static/<path:filename>")
@require_auth
def static_file(filename):
    return send_from_directory(STATIC_DIR, filename)


# ─── GET API ───────────────────────────────────

@app.route("/api/tasks", methods=["GET"])
@require_auth
def api_get_tasks():
    return jsonify(kanban.load_data())

@app.route("/api/ui_state", methods=["GET"])
@require_auth
def api_get_ui_state():
    return jsonify(kanban.load_data().get("ui_state", {}))

@app.route("/api/archive", methods=["GET"])
@require_auth
def api_get_archive():
    return jsonify(kanban.load_data().get("archive", []))

@app.route("/api/conflicts", methods=["GET"])
@require_auth
def api_get_conflicts():
    return jsonify({"conflicts": kanban.check_conflicts()})

@app.route("/api/reminders", methods=["GET"])
@require_auth
def api_get_reminders():
    return jsonify(kanban.load_data().get("reminders", []))

@app.route("/api/reminders/due", methods=["GET"])
@require_auth
def api_get_due_reminders():
    data = kanban.load_data()
    reminders = data.get("reminders", [])
    now = datetime.now(kanban.SYDNEY_TZ)
    due = []
    for r in reminders:
        if not r.get("active", False):
            continue
        try:
            next_fire = datetime.fromisoformat(r["next_fire_at"])
        except (KeyError, ValueError):
            continue
        if next_fire <= now:
            if r.get("last_fired_at"):
                try:
                    last_fired = datetime.fromisoformat(r["last_fired_at"])
                    if (now - last_fired).total_seconds() < 120:
                        continue
                except (ValueError, TypeError):
                    pass
            due.append(r)
    return jsonify(due)

@app.route("/api/calendar", methods=["GET"])
@require_auth
def api_get_calendar():
    return jsonify(kanban._fetch_calendar_events())


# ─── POST API ──────────────────────────────────

def _body():
    return request.get_json(silent=True) or {}

def _now_iso():
    return datetime.now().isoformat(timespec="seconds")

@app.route("/api/tasks", methods=["POST"])
@require_auth
def api_create_task():
    body = _body()
    data = kanban.load_data()
    task = {
        "id": uuid.uuid4().hex[:8],
        "title": body.get("title", "New task"),
        "explainer": body.get("explainer"),
        "project_id": body.get("project_id", data["projects"][0]["id"] if data["projects"] else "general"),
        "priority": body.get("priority", "P2"),
        "status": body.get("status", "todo"),
        "done": False,
        "created_at": _now_iso(),
        "updated_at": _now_iso(),
        "completed_at": None,
        "sort_order": len(data["tasks"]),
        "substep": body.get("substep"),
        "timer_started_at": None,
        "timer_elapsed": 0,
        "timer_first_started": None,
        "timer_sessions": []
    }
    data["tasks"].append(task)
    kanban.save_data(data)
    return jsonify(task), 201

@app.route("/api/tasks/reorder", methods=["POST"])
@require_auth
def api_reorder_tasks():
    body = _body()
    data = kanban.load_data()
    order_map = {item["id"]: item["sort_order"] for item in body.get("order", [])}
    for task in data["tasks"]:
        if task["id"] in order_map:
            task["sort_order"] = order_map[task["id"]]
    kanban.save_data(data)
    return jsonify({"ok": True})

@app.route("/api/tasks/<task_id>/move", methods=["POST"])
@require_auth
def api_move_task(task_id):
    body = _body()
    data = kanban.load_data()
    task = next((t for t in data["tasks"] if t["id"] == task_id), None)
    if not task:
        task = next((t for t in data["archive"] if t["id"] == task_id), None)
        if task:
            data["archive"].remove(task)
            data["tasks"].append(task)
    if not task:
        return jsonify({"error": "Task not found"}), 404

    if "status" in body:
        task["status"] = body["status"]
        task["done"] = body["status"] == "done"
        task["completed_at"] = _now_iso() if task["done"] else None
    for key in ("priority", "project_id", "timer_started_at", "timer_elapsed",
                "timer_first_started", "timer_sessions"):
        if key in body:
            task[key] = body[key]
    if "sort_order" in body:
        new_order = body["sort_order"]
        for t in data["tasks"]:
            if t["id"] != task["id"] and t.get("sort_order", 0) >= new_order:
                t["sort_order"] = t.get("sort_order", 0) + 1
        task["sort_order"] = new_order

    task["updated_at"] = _now_iso()
    kanban.save_data(data)
    return jsonify(task)

@app.route("/api/archive/clear", methods=["POST"])
@require_auth
def api_clear_done():
    data = kanban.load_data()
    done_tasks = [t for t in data["tasks"] if t["done"]]
    data["tasks"] = [t for t in data["tasks"] if not t["done"]]
    data["archive"].extend(done_tasks)
    kanban.save_data(data)
    return jsonify({"archived": len(done_tasks)})

@app.route("/api/archive/selected", methods=["POST"])
@require_auth
def api_archive_selected():
    body = _body()
    ids = set(body.get("ids", []))
    data = kanban.load_data()
    to_archive = [t for t in data["tasks"] if t["id"] in ids]
    data["tasks"] = [t for t in data["tasks"] if t["id"] not in ids]
    data["archive"].extend(to_archive)
    kanban.save_data(data)
    return jsonify({"archived": len(to_archive)})

@app.route("/api/restore", methods=["POST"])
@require_auth
def api_restore():
    body = _body()
    if "tasks" not in body or "projects" not in body:
        return jsonify({"error": "Invalid restore data"}), 400
    kanban.save_data(body)
    return jsonify({"ok": True})

@app.route("/api/projects", methods=["POST"])
@require_auth
def api_create_project():
    body = _body()
    data = kanban.load_data()
    project = {
        "id": body.get("id") or uuid.uuid4().hex[:8],
        "name": body.get("name", "New Project"),
        "sort_order": len(data["projects"])
    }
    data["projects"].append(project)
    kanban.save_data(data)
    return jsonify(project), 201

@app.route("/api/projects/sync", methods=["POST"])
@require_auth
def api_sync_projects():
    return jsonify(kanban.sync_projects())

@app.route("/api/ui_state", methods=["POST"])
@require_auth
def api_save_ui_state():
    body = _body()
    data = kanban.load_data()
    data["ui_state"] = body
    kanban.save_data(data)
    return jsonify({"ok": True})

@app.route("/api/reminders", methods=["POST"])
@require_auth
def api_create_reminder():
    body = _body()
    data = kanban.load_data()
    reminder = {
        "id": uuid.uuid4().hex[:8],
        "title": body.get("title", "New Reminder"),
        "details": body.get("details") or None,
        "time": body.get("time", "09:00"),
        "frequency": body.get("frequency", "daily"),
        "interval": body.get("interval", 1) or 1,
        "days_of_week": body.get("days_of_week", [0]),
        "day_of_month": body.get("day_of_month"),
        "month": body.get("month"),
        "ends": body.get("ends", "never"),
        "end_date": body.get("end_date"),
        "occurrences_left": body.get("occurrences_left"),
        "last_fired_at": None,
        "active": True,
        "created_at": datetime.now(kanban.SYDNEY_TZ).isoformat()
    }
    reminder["next_fire_at"] = kanban.calculate_next_fire(reminder)
    data["reminders"].append(reminder)
    kanban.save_data(data)
    return jsonify(reminder), 201

@app.route("/api/reminders/<reminder_id>/fire", methods=["POST"])
@require_auth
def api_fire_reminder(reminder_id):
    data = kanban.load_data()
    reminder = next((r for r in data["reminders"] if r["id"] == reminder_id), None)
    if not reminder:
        return jsonify({"error": "Reminder not found"}), 404

    now = datetime.now(kanban.SYDNEY_TZ)
    reminder["last_fired_at"] = now.isoformat()

    if reminder["frequency"] == "once":
        reminder["active"] = False
    else:
        reminder["next_fire_at"] = kanban.calculate_next_fire(reminder)
        ends = reminder.get("ends", "never")
        if ends == "on" and reminder.get("end_date"):
            try:
                end_dt = datetime.fromisoformat(reminder["end_date"])
                if not end_dt.tzinfo:
                    end_dt = end_dt.replace(tzinfo=kanban.SYDNEY_TZ)
                next_dt = datetime.fromisoformat(reminder["next_fire_at"])
                if next_dt > end_dt:
                    reminder["active"] = False
            except (ValueError, TypeError):
                pass
        elif ends == "after" and reminder.get("occurrences_left") is not None:
            reminder["occurrences_left"] = max(0, reminder["occurrences_left"] - 1)
            if reminder["occurrences_left"] <= 0:
                reminder["active"] = False

    kanban.save_data(data)
    return jsonify(reminder)

@app.route("/api/calendar/refresh", methods=["POST"])
@require_auth
def api_refresh_calendar():
    events = kanban._fetch_calendar_events(force=True)
    return jsonify({"refreshed": len(events)})


# ─── PUT API ───────────────────────────────────

@app.route("/api/tasks/<task_id>", methods=["PUT"])
@require_auth
def api_update_task(task_id):
    body = _body()
    data = kanban.load_data()
    task = next((t for t in data["tasks"] if t["id"] == task_id), None)
    if not task:
        return jsonify({"error": "Task not found"}), 404

    for key in ("title", "explainer", "project_id", "priority", "status", "substep",
                "sort_order", "timer_started_at", "timer_elapsed",
                "timer_first_started", "timer_sessions"):
        if key in body:
            task[key] = body[key]

    if "status" in body:
        task["done"] = body["status"] == "done"
        if task["done"] and not task.get("completed_at"):
            task["completed_at"] = _now_iso()
        elif not task["done"]:
            task["completed_at"] = None

    task["updated_at"] = _now_iso()
    kanban.save_data(data)
    return jsonify(task)

@app.route("/api/reminders/<reminder_id>", methods=["PUT"])
@require_auth
def api_update_reminder(reminder_id):
    body = _body()
    data = kanban.load_data()
    reminder = next((r for r in data["reminders"] if r["id"] == reminder_id), None)
    if not reminder:
        return jsonify({"error": "Reminder not found"}), 404

    recalc_fields = {"time", "frequency", "interval", "days_of_week", "day_of_month", "month"}
    needs_recalc = False
    for key in ("title", "details", "time", "frequency", "interval", "days_of_week",
                "day_of_month", "month", "ends", "end_date", "occurrences_left", "active"):
        if key in body:
            reminder[key] = body[key]
            if key in recalc_fields:
                needs_recalc = True

    if needs_recalc:
        reminder["next_fire_at"] = kanban.calculate_next_fire(reminder)

    kanban.save_data(data)
    return jsonify(reminder)

@app.route("/api/projects/<project_id>", methods=["PUT"])
@require_auth
def api_update_project(project_id):
    body = _body()
    data = kanban.load_data()
    project = next((p for p in data["projects"] if p["id"] == project_id), None)
    if not project:
        return jsonify({"error": "Project not found"}), 404

    for key in ("name", "sort_order", "visible"):
        if key in body:
            project[key] = body[key]

    kanban.save_data(data)
    return jsonify(project)


# ─── DELETE API ────────────────────────────────

@app.route("/api/tasks/<task_id>", methods=["DELETE"])
@require_auth
def api_delete_task(task_id):
    data = kanban.load_data()
    before = len(data["tasks"])
    data["tasks"] = [t for t in data["tasks"] if t["id"] != task_id]
    if len(data["tasks"]) == before:
        return jsonify({"error": "Task not found"}), 404
    kanban.save_data(data)
    return jsonify({"ok": True})

@app.route("/api/projects/<project_id>", methods=["DELETE"])
@require_auth
def api_delete_project(project_id):
    data = kanban.load_data()
    data["projects"] = [p for p in data["projects"] if p["id"] != project_id]
    kanban.save_data(data)
    return jsonify({"ok": True})

@app.route("/api/reminders/<reminder_id>", methods=["DELETE"])
@require_auth
def api_delete_reminder(reminder_id):
    data = kanban.load_data()
    before = len(data.get("reminders", []))
    data["reminders"] = [r for r in data.get("reminders", []) if r["id"] != reminder_id]
    if len(data["reminders"]) == before:
        return jsonify({"error": "Reminder not found"}), 404
    kanban.save_data(data)
    return jsonify({"ok": True})


# ─── Local dev runner ──────────────────────────

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8090))
    if not AUTH_PASS:
        print("WARNING: KANBAN_PASS not set — all requests will return 500.")
    print(f"Kanban (Flask) running at http://localhost:{port}")
    print(f"Data file: {kanban.DATA_FILE}")
    app.run(host="127.0.0.1", port=port, debug=False)
