"""File-based JSON storage layer.

All data lives in two JSON files under STORAGE_DIR:
  - users.json : list of user records
  - tasks.json : list of task records

Reads and writes are serialized through a process-wide lock and written
atomically (temp file + os.replace) so concurrent requests can't corrupt
the files. Suitable for the small, single-team scale this app targets.
"""
import json
import os
import threading

from .timeutils import now_utc_iso

_LOCK = threading.RLock()
_STORAGE_DIR = None

USERS_FILE = "users.json"
TASKS_FILE = "tasks.json"
NOTIFICATIONS_FILE = "notifications.json"
SUBSCRIPTIONS_FILE = "subscriptions.json"


def init_storage(storage_dir):
    """Point the storage layer at a directory and ensure files exist."""
    global _STORAGE_DIR
    _STORAGE_DIR = storage_dir
    os.makedirs(_STORAGE_DIR, exist_ok=True)
    for name in (USERS_FILE, TASKS_FILE, NOTIFICATIONS_FILE, SUBSCRIPTIONS_FILE):
        path = os.path.join(_STORAGE_DIR, name)
        if not os.path.exists(path):
            _write_raw(path, [])


def now_iso():
    return now_utc_iso()


def _path(name):
    if _STORAGE_DIR is None:
        raise RuntimeError("storage not initialized; call init_storage() first")
    return os.path.join(_STORAGE_DIR, name)


def _read_raw(path):
    try:
        with open(path, "r", encoding="utf-8") as fh:
            return json.load(fh)
    except (FileNotFoundError, json.JSONDecodeError):
        return []


def _write_raw(path, data):
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as fh:
        json.dump(data, fh, indent=2, ensure_ascii=False)
    os.replace(tmp, path)


def _load(name):
    return _read_raw(_path(name))


def _save(name, data):
    _write_raw(_path(name), data)


def _next_id(records):
    return (max((r["id"] for r in records), default=0)) + 1


# --------------------------------------------------------------------------
# Users
# --------------------------------------------------------------------------
def get_users(include_inactive=True):
    with _LOCK:
        users = _load(USERS_FILE)
    if include_inactive:
        return users
    return [u for u in users if u.get("active", True)]


def get_user(user_id):
    for u in get_users():
        if u["id"] == user_id:
            return u
    return None


def get_user_by_email(email):
    if not email:
        return None
    email = email.strip().lower()
    for u in get_users():
        if u["email"].strip().lower() == email:
            return u
    return None


def create_user(email, name, role, active=True, lead_id=None):
    with _LOCK:
        users = _load(USERS_FILE)
        user = {
            "id": _next_id(users),
            "email": email.strip().lower(),
            "name": name.strip(),
            "role": role,
            "active": active,
            "lead_id": lead_id,
            "created_at": now_iso(),
        }
        users.append(user)
        _save(USERS_FILE, users)
        return user


def update_user(user_id, **changes):
    with _LOCK:
        users = _load(USERS_FILE)
        updated = None
        for u in users:
            if u["id"] == user_id:
                u.update(changes)
                updated = u
                break
        if updated is not None:
            _save(USERS_FILE, users)
        return updated


def get_subordinates(lead_id=None, include_inactive=False):
    users = get_users()
    subs = [u for u in users if u["role"] == "subordinate"]
    if lead_id is not None:
        subs = [u for u in subs if u.get("lead_id") == lead_id]
    if not include_inactive:
        subs = [u for u in subs if u.get("active", True)]
    return sorted(subs, key=lambda u: u["name"].lower())


# --------------------------------------------------------------------------
# Tasks
# --------------------------------------------------------------------------
def get_tasks():
    with _LOCK:
        return _load(TASKS_FILE)


def get_task(task_id):
    for t in get_tasks():
        if t["id"] == task_id:
            return t
    return None


def create_task(title, description, due_date, assignee_id, created_by):
    with _LOCK:
        tasks = _load(TASKS_FILE)
        task = {
            "id": _next_id(tasks),
            "title": title.strip(),
            "description": (description or "").strip(),
            "due_date": due_date,  # "YYYY-MM-DD" or ""
            "status": "open",
            "assignee_id": assignee_id,
            "created_by": created_by,
            "created_at": now_iso(),
            "completed_at": None,
        }
        tasks.append(task)
        _save(TASKS_FILE, tasks)
        return task


def update_task(task_id, **changes):
    with _LOCK:
        tasks = _load(TASKS_FILE)
        updated = None
        for t in tasks:
            if t["id"] == task_id:
                t.update(changes)
                updated = t
                break
        if updated is not None:
            _save(TASKS_FILE, tasks)
        return updated


def delete_task(task_id):
    with _LOCK:
        tasks = _load(TASKS_FILE)
        remaining = [t for t in tasks if t["id"] != task_id]
        if len(remaining) != len(tasks):
            _save(TASKS_FILE, remaining)
            return True
        return False


def complete_task(task_id):
    return update_task(task_id, status="completed", completed_at=now_iso())


def reopen_task(task_id):
    return update_task(task_id, status="open", completed_at=None)


def tasks_for_assignee(assignee_id, status=None):
    tasks = [t for t in get_tasks() if t["assignee_id"] == assignee_id]
    if status:
        tasks = [t for t in tasks if t["status"] == status]
    return tasks


# --------------------------------------------------------------------------
# Notifications
# --------------------------------------------------------------------------
def add_notification(user_id, ntype, title, body, task_id=None):
    with _LOCK:
        items = _load(NOTIFICATIONS_FILE)
        note = {
            "id": _next_id(items),
            "user_id": user_id,
            "type": ntype,
            "title": title,
            "body": body,
            "task_id": task_id,
            "read": False,
            "created_at": now_iso(),
        }
        items.append(note)
        _save(NOTIFICATIONS_FILE, items)
        return note


def get_notifications(user_id, limit=100):
    with _LOCK:
        items = [n for n in _load(NOTIFICATIONS_FILE) if n["user_id"] == user_id]
    items.sort(key=lambda n: n.get("created_at", ""), reverse=True)
    return items[:limit]


def unread_count(user_id):
    with _LOCK:
        return sum(
            1 for n in _load(NOTIFICATIONS_FILE)
            if n["user_id"] == user_id and not n.get("read")
        )


def mark_all_read(user_id):
    with _LOCK:
        items = _load(NOTIFICATIONS_FILE)
        changed = False
        for n in items:
            if n["user_id"] == user_id and not n.get("read"):
                n["read"] = True
                changed = True
        if changed:
            _save(NOTIFICATIONS_FILE, items)


# --------------------------------------------------------------------------
# Web-push subscriptions
# --------------------------------------------------------------------------
def add_subscription(user_id, subscription):
    endpoint = subscription.get("endpoint")
    if not endpoint:
        return None
    with _LOCK:
        subs = _load(SUBSCRIPTIONS_FILE)
        # De-dupe by endpoint; keep it tied to the current user.
        subs = [s for s in subs if s.get("subscription", {}).get("endpoint") != endpoint]
        record = {
            "id": _next_id(subs),
            "user_id": user_id,
            "subscription": subscription,
            "created_at": now_iso(),
        }
        subs.append(record)
        _save(SUBSCRIPTIONS_FILE, subs)
        return record


def get_subscriptions(user_id):
    with _LOCK:
        return [s for s in _load(SUBSCRIPTIONS_FILE) if s["user_id"] == user_id]


def remove_subscription(endpoint):
    with _LOCK:
        subs = _load(SUBSCRIPTIONS_FILE)
        remaining = [s for s in subs if s.get("subscription", {}).get("endpoint") != endpoint]
        if len(remaining) != len(subs):
            _save(SUBSCRIPTIONS_FILE, remaining)
