"""
biosync licensing portal — accounts, subscriptions, payments, and license keys.

Flow: register -> subscribe (pick a plan) -> pay (via the configured provider) ->
on payment confirmed the server mints a signed license key and shows it on the
account dashboard. An admin view monitors users, payments and licenses.

Run:  python3 -m license_server          (defaults to the mock payment provider)
Env:  BIOSYNC_PORTAL_SECRET   Flask session secret (set in production)
      BIOSYNC_ADMIN_EMAIL     this account becomes admin on registration
      BIOSYNC_LICENSE_PRIVKEY signing private key (else license_server/private_key.hex)
      BIOSYNC_PAYMENT_PROVIDER mock | stripe (default mock)
"""

from __future__ import annotations

import os
import time
from datetime import date, datetime, timedelta

from flask import (Flask, request, redirect, url_for, session, abort,
                   render_template_string)
from werkzeug.security import generate_password_hash, check_password_hash

from .db import DB
from .payments import get_provider, new_ref
from .keys import issue_license, PLANS


def create_app(db_path: str = "license_server/portal.db") -> Flask:
    app = Flask(__name__)
    app.secret_key = os.environ.get("BIOSYNC_PORTAL_SECRET", os.urandom(24).hex())
    db = DB(db_path)
    provider = get_provider()
    admin_email = os.environ.get("BIOSYNC_ADMIN_EMAIL", "").lower()

    # --- helpers ---------------------------------------------------------
    def current():
        uid = session.get("uid")
        return db.user(uid) if uid else None

    def require_login():
        u = current()
        if not u:
            abort(401)
        return u

    @app.route("/")
    def index():
        return redirect("/account" if current() else "/login")

    # --- auth ------------------------------------------------------------
    @app.route("/register", methods=["GET", "POST"])
    def register():
        if request.method == "POST":
            email = (request.form.get("email") or "").strip().lower()
            pw = request.form.get("password") or ""
            if not email or len(pw) < 8:
                return page("Register", _register_form("Enter an email and an 8+ char password."))
            if db.user_by_email(email):
                return page("Register", _register_form("That email already has an account."))
            uid = db.create_user(email, request.form.get("name", ""),
                                 request.form.get("org", ""), generate_password_hash(pw),
                                 is_admin=1 if email == admin_email else 0)
            _send_verification(db, uid, email)
            session["uid"] = uid
            return redirect("/account")
        return page("Register", _register_form())

    @app.route("/verify/<token>")
    def verify(token):
        uid = db.use_token(token, "verify")
        if not uid:
            return page("Verify", "<div class='card'><h1>Link expired</h1>"
                        "<p class='muted'>That verification link is invalid or used. "
                        "<a href='/account'>Resend from your account</a>.</p></div>")
        db.set_verified(uid, 1)
        session["uid"] = uid
        return redirect("/account")

    @app.route("/resend-verification", methods=["POST"])
    def resend_verification():
        u = require_login()
        _send_verification(db, u["id"], u["email"])
        return redirect("/account")

    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            u = db.user_by_email((request.form.get("email") or "").strip().lower())
            if u and check_password_hash(u["pw_hash"], request.form.get("password") or ""):
                session["uid"] = u["id"]
                return redirect("/account")
            return page("Sign in", _login_form("Wrong email or password."))
        return page("Sign in", _login_form())

    @app.route("/forgot", methods=["GET", "POST"])
    def forgot():
        if request.method == "POST":
            email = (request.form.get("email") or "").strip().lower()
            u = db.user_by_email(email)
            if u:
                tok = db.create_token(u["id"], "reset", ttl=3600)
                from .email_send import send_email, portal_url
                send_email(email, "Reset your biosync password",
                           f"Reset your password (valid 1 hour):\n{portal_url()}/reset/{tok}\n"
                           "If you didn't request this, ignore this email.")
            # always show success (don't reveal whether the email exists)
            return page("Reset", "<div class='card'><h1>Check your email</h1>"
                        "<p class='muted'>If that address has an account, a reset link is on its way.</p></div>")
        return page("Reset", _forgot_form())

    @app.route("/reset/<token>", methods=["GET", "POST"])
    def reset(token):
        if request.method == "POST":
            pw = request.form.get("password") or ""
            if len(pw) < 8:
                return page("Reset", _reset_form(token, "Password must be 8+ characters."))
            uid = db.use_token(token, "reset")
            if not uid:
                return page("Reset", "<div class='card'><h1>Link expired</h1>"
                            "<p class='muted'>Request a new one on the "
                            "<a href='/forgot'>reset page</a>.</p></div>")
            db.set_password(uid, generate_password_hash(pw))
            return redirect("/login")
        return page("Reset", _reset_form(token))

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

    # --- account dashboard ----------------------------------------------
    @app.route("/account")
    def account():
        u = require_login()
        lic = db.latest_license(u["id"])
        sub = db.latest_subscription(u["id"])
        pay = db.latest_payment(u["id"])
        return page("Account", _account_view(u, lic, sub, pay))

    # --- subscribe + pay -------------------------------------------------
    @app.route("/subscribe", methods=["POST"])
    def subscribe():
        u = require_login()
        if not u["verified"]:
            return redirect("/account")      # must confirm email before paying
        plan = request.form.get("plan", "annual")
        days, price, cur, label, period = PLANS.get(plan, PLANS["annual"])
        ref = new_ref()
        sub_id = db.ins("INSERT INTO subscriptions(user_id,plan,status,started,renews,provider_ref)"
                        " VALUES(?,?,?,?,?,?)",
                        (u["id"], plan, "pending", time.time(),
                         time.time() + days * 86400, ref))
        db.ins("INSERT INTO payments(user_id,subscription_id,amount,currency,status,provider,provider_ref,created)"
               " VALUES(?,?,?,?,?,?,?,?)",
               (u["id"], sub_id, price, cur, "pending", provider.name, ref, time.time()))
        co = provider.create_checkout(amount=price, currency=cur, email=u["email"],
                                      ref=ref, plan=plan)
        return redirect(co["url"])

    @app.route("/pay/confirm")
    def pay_confirm():
        # mock-provider redirect (and a manual confirm for testing real flows)
        u = require_login()
        res = provider.verify_callback(request.args.to_dict())
        return _settle(db, u, res)

    @app.route("/pay/return")
    def pay_return():
        # gateway redirects the user back here; we re-verify with the gateway API
        u = require_login()
        res = provider.verify_callback(request.args.to_dict())
        return _settle(db, u, res)

    @app.route("/pay/webhook", methods=["POST"])
    def pay_webhook():
        # real providers post here. Verify the signature BEFORE trusting anything.
        if not provider.verify_webhook(request.get_data(), request.headers):
            abort(400)
        body = request.get_json(force=True, silent=True) or {}
        data = body.get("data", body)
        res = provider.verify_callback({"ref": data.get("reference")})
        pay = db.q("SELECT * FROM payments WHERE provider_ref=?", (res.get("ref"),), one=True)
        if pay and res.get("paid"):
            _settle(db, db.user(pay["user_id"]), res)
        return {"ok": True}

    # --- admin -----------------------------------------------------------
    @app.route("/admin")
    def admin():
        u = require_login()
        if not u["is_admin"]:
            abort(403)
        return page("Admin", _admin_view(db))

    @app.route("/admin/revoke", methods=["POST"])
    def admin_revoke():
        u = require_login()
        if not u["is_admin"]:
            abort(403)
        db.q("UPDATE licenses SET status='revoked' WHERE license_id=?",
             (request.form.get("license_id"),))
        return redirect("/admin")

    @app.route("/admin/grant", methods=["POST"])
    def admin_grant():
        """Manually issue a key to an email (creates the account if needed).
        Use for comps, enterprise deals, or re-issuing after a problem."""
        u = require_login()
        if not u["is_admin"]:
            abort(403)
        email = (request.form.get("email") or "").strip().lower()
        plan = request.form.get("plan", "annual")
        days = request.form.get("days")
        days = int(days) if days else None
        if not email:
            return redirect("/admin")
        target = db.user_by_email(email)
        if not target:
            # create a placeholder account; they set a password via "forgot password"
            import secrets
            from werkzeug.security import generate_password_hash
            uid = db.create_user(email, request.form.get("name", ""), "",
                                 generate_password_hash(secrets.token_hex(16)))
            target = db.user(uid)
        grant_license(db, target, plan, days)
        return redirect("/admin")

    @app.route("/admin/disable", methods=["POST"])
    def admin_disable():
        u = require_login()
        if not u["is_admin"]:
            abort(403)
        # revoke all of a user's active licenses (kills access on next online check)
        db.q("UPDATE licenses SET status='revoked' WHERE user_id=? AND status='active'",
             (request.form.get("user_id"),))
        return redirect("/admin")

    @app.errorhandler(401)
    def _401(e):
        return redirect("/login")

    return app


# --------------------------------------------------------------------------
def grant_license(db, user, plan="annual", days=None):
    """Mint + store a signed license key for a user (revoking any active one).
    Used by the payment flow AND by manual admin/CLI grants."""
    import time as _t
    db.q("UPDATE licenses SET status='revoked' WHERE user_id=? AND status='active'",
         (user["id"],))
    lic = issue_license(user["email"], plan, days=days)
    db.ins("INSERT INTO licenses(user_id,license_id,key,plan,issued,expires,status,created)"
           " VALUES(?,?,?,?,?,?,?,?)",
           (user["id"], lic["license_id"], lic["key"], lic["plan"],
            lic["issued"], lic["expires"], "active", _t.time()))
    return lic


def _settle(db, user, res):
    """Mark the payment paid and mint a license key (idempotent per ref)."""
    ref = res.get("ref")
    pay = db.q("SELECT * FROM payments WHERE provider_ref=?", (ref,), one=True)
    if not pay or not res.get("paid"):
        return redirect("/account")
    if pay["status"] != "paid":
        db.q("UPDATE payments SET status='paid', paid_at=? WHERE id=?",
             (time.time(), pay["id"]))
        db.q("UPDATE subscriptions SET status='active' WHERE id=?",
             (pay["subscription_id"],))
        sub = db.q("SELECT * FROM subscriptions WHERE id=?", (pay["subscription_id"],), one=True)
        grant_license(db, user, sub["plan"])
    return redirect("/account")


# ---------- HTML (themed, minimal) ----------
_CSS = """
:root{--p:#2f6470;--a:#3a9aa8;--ink:#16252a;--muted:#5f7378;--line:#e3edef;--bg:#eef2f4}
*{box-sizing:border-box}body{margin:0;font-family:'Inter',system-ui,Arial;background:var(--bg);color:var(--ink)}
.top{background:#0e1417;color:#fff;padding:14px 20px;display:flex;align-items:center;gap:16px}
.top a{color:#cde0e4;text-decoration:none;font-size:14px}.top a:hover{color:#fff}.top .sp{flex:1}
.brand{font-weight:800;letter-spacing:-.02em}
.wrap{max-width:760px;margin:26px auto;padding:0 18px;display:grid;gap:16px}
.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:22px;box-shadow:0 1px 2px rgba(8,20,24,.05)}
h1{font-size:22px;margin:0 0 4px;color:var(--p)}h2{font-size:15px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin:0 0 12px}
label{display:block;font-size:13px;color:var(--muted);margin:10px 0 4px}
input,select{width:100%;padding:10px;border:1px solid var(--line);border-radius:9px;font-size:14px}
.btn{background:var(--p);color:#fff;border:0;border-radius:10px;padding:11px 18px;font-weight:650;font-size:15px;cursor:pointer;text-decoration:none;display:inline-block}
.btn:hover{background:#27545f}.ghost{background:#fff;color:var(--p);border:1px solid var(--line)}
.key{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#0e1417;color:#9be7d6;padding:12px;border-radius:8px;word-break:break-all}
.pill{display:inline-block;padding:2px 10px;border-radius:999px;font-weight:700;font-size:12px}
.ok{background:#1f7a4d;color:#fff}.pend{background:#c9772a;color:#fff}.no{background:#b23b2e;color:#fff}
table{width:100%;border-collapse:collapse;font-size:13px}td,th{padding:7px 8px;border-bottom:1px solid var(--line);text-align:left}
.err{color:#b23b2e;font-size:13px;margin-bottom:8px}.muted{color:var(--muted);font-size:13px}
"""


def page(title, body, user=None):
    nav = ('<a href="/account">Account</a>'
           '<span class="sp"></span><a href="/logout">Sign out</a>')
    return render_template_string(
        "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' "
        "content='width=device-width, initial-scale=1'><title>biosync — {{t}}</title>"
        "<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap' rel='stylesheet'>"
        "<style>{{css}}</style></head><body>"
        "<div class='top'><span class='brand'>biosync</span><span class='muted' style='color:#9fb3ba'>licensing portal</span>"
        "<span class='sp'></span><a href='/account'>Account</a><a href='/admin'>Admin</a><a href='/logout'>Sign out</a></div>"
        "<div class='wrap'>{{body|safe}}</div></body></html>",
        t=title, css=_CSS, body=body)


def _register_form(err=""):
    e = f"<div class='err'>{err}</div>" if err else ""
    return ("<div class='card'><h1>Create your account</h1>"
            "<p class='muted'>An account lets us assign your license and track your subscription.</p>"
            f"<form method='post'>{e}"
            "<label>Full name</label><input name='name'>"
            "<label>Organisation</label><input name='org'>"
            "<label>Email</label><input name='email' type='email' required>"
            "<label>Password (8+ characters)</label><input name='password' type='password' required>"
            "<div style='margin-top:14px'><button class='btn'>Create account</button> "
            "<a class='muted' href='/login' style='margin-left:10px'>Already have one? Sign in</a></div></form></div>")


def _login_form(err=""):
    e = f"<div class='err'>{err}</div>" if err else ""
    return ("<div class='card'><h1>Sign in</h1>"
            f"<form method='post'>{e}"
            "<label>Email</label><input name='email' type='email' required>"
            "<label>Password</label><input name='password' type='password' required>"
            "<div style='margin-top:14px'><button class='btn'>Sign in</button> "
            "<a class='muted' href='/register' style='margin-left:10px'>Create an account</a>"
            "<a class='muted' href='/forgot' style='margin-left:10px'>Forgot password?</a></div></form></div>")


def _forgot_form(err=""):
    e = f"<div class='err'>{err}</div>" if err else ""
    return ("<div class='card'><h1>Reset your password</h1>"
            "<p class='muted'>Enter your account email and we'll send a reset link.</p>"
            f"<form method='post'>{e}"
            "<label>Email</label><input name='email' type='email' required>"
            "<div style='margin-top:14px'><button class='btn'>Send reset link</button> "
            "<a class='muted' href='/login' style='margin-left:10px'>Back to sign in</a></div></form></div>")


def _reset_form(token, err=""):
    e = f"<div class='err'>{err}</div>" if err else ""
    return ("<div class='card'><h1>Choose a new password</h1>"
            f"<form method='post' action='/reset/{token}'>{e}"
            "<label>New password (8+ characters)</label><input name='password' type='password' required>"
            "<div style='margin-top:14px'><button class='btn'>Set password</button></div></form></div>")


def _send_verification(db, uid, email):
    tok = db.create_token(uid, "verify", ttl=86400)
    from .email_send import send_email, portal_url
    send_email(email, "Confirm your biosync email",
               f"Welcome to biosync! Confirm your email to activate your account:\n"
               f"{portal_url()}/verify/{tok}\nThis link is valid for 24 hours.")


def _account_view(u, lic, sub, pay):
    # subscription / payment status
    sub_status = sub["status"] if sub else "none"
    pay_status = pay["status"] if pay else "none"
    cls = {"active": "ok", "paid": "ok", "pending": "pend", "none": "no",
           "failed": "no", "canceled": "no"}.get(sub_status, "pend")
    paycls = {"paid": "ok", "pending": "pend", "failed": "no", "none": "no"}.get(pay_status, "pend")

    verify_banner = ""
    if not u["verified"]:
        verify_banner = (
            "<div class='card' style='border-color:#c9772a'>"
            "<b style='color:#c9772a'>Confirm your email</b>"
            "<p class='muted' style='margin:6px 0 10px'>We sent a confirmation link to "
            f"{u['email']}. You must confirm before subscribing.</p>"
            "<form method='post' action='/resend-verification' style='display:inline'>"
            "<button class='btn ghost'>Resend link</button></form></div>")

    head = (verify_banner
            + f"<div class='card'><h1>Hello, {u['name'] or u['email']}</h1>"
            f"<p class='muted'>{u['email']}</p>"
            f"<div style='margin-top:8px'>Subscription: <span class='pill {cls}'>{sub_status.upper()}</span> "
            f"&nbsp; Payment: <span class='pill {paycls}'>{pay_status.upper()}</span></div></div>")

    if lic and lic["status"] == "active" and date.today().isoformat() <= lic["expires"]:
        days = (datetime.strptime(lic["expires"], "%Y-%m-%d").date() - date.today()).days
        license_card = (
            "<div class='card'><h2>Your license key</h2>"
            "<p class='muted' style='margin-top:0'>Paste this into biosync → License to activate. "
            f"Valid until <b>{lic['expires']}</b> ({days} days).</p>"
            f"<div class='key'>{lic['key']}</div>"
            f"<p class='muted' style='margin-top:8px'>License ID {lic['license_id']} · plan {lic['plan']}</p></div>")
    else:
        opts = "".join(
            f"<option value='{k}'>{lbl} — R{price/100:,.0f} / {period}</option>"
            for k, (d, price, cur, lbl, period) in PLANS.items())
        revoked = (lic and lic["status"] == "revoked")
        note = "<p class='err'>A previous license was revoked. Subscribe to get a new key.</p>" if revoked else ""
        license_card = (
            "<div class='card'><h2>Get a license</h2>"
            "<p class='muted' style='margin-top:0'>Licenses are issued with an annual subscription. "
            "After payment your key appears here instantly.</p>" + note +
            "<form method='post' action='/subscribe'>"
            f"<label>Plan</label><select name='plan'>{opts}</select>"
            "<div style='margin-top:14px'><button class='btn'>Subscribe &amp; pay</button></div></form></div>")

    admin_link = "<div class='card muted'>You are an admin — <a href='/admin'>open the admin dashboard</a>.</div>" if u["is_admin"] else ""
    return head + license_card + admin_link


def _admin_view(db):
    users = db.all_users()
    licenses = db.all_licenses()
    n_active = sum(1 for l in licenses if l["status"] == "active")
    paid = [db.latest_payment(u["id"]) for u in users]
    n_paid = sum(1 for p in paid if p and p["status"] == "paid")

    plan_opts = "".join(f"<option value='{k}'>{lbl}</option>"
                        for k, (d, pr, cur, lbl, per) in PLANS.items())

    # summary + manual grant
    summary = (
        "<div class='card'><h1>Admin · manage</h1>"
        f"<p class='muted'>{len(users)} accounts · {n_paid} paying · {n_active} active licenses</p></div>"
        "<div class='card'><h2>Issue a license manually</h2>"
        "<p class='muted' style='margin-top:0'>Grant a key without payment (comps, enterprise, re-issue). "
        "Creates the account if the email is new.</p>"
        "<form method='post' action='/admin/grant'>"
        "<div style='display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end'>"
        "<div style='flex:1;min-width:200px'><label>Email</label><input name='email' type='email' required></div>"
        f"<div><label>Plan</label><select name='plan'>{plan_opts}</select></div>"
        "<div><label>Days (optional)</label><input name='days' type='number' placeholder='plan default' style='width:120px'></div>"
        "<button class='btn'>Issue key</button></div></form></div>")

    rows = ""
    for u in users:
        lic = db.latest_license(u["id"])
        pay = db.latest_payment(u["id"])
        lic_s = (f"<span class='pill {'ok' if lic and lic['status']=='active' else 'no'}'>"
                 f"{lic['status'] if lic else 'none'}</span>")
        pay_s = (f"<span class='pill {'ok' if pay and pay['status']=='paid' else 'pend' if pay else 'no'}'>"
                 f"{pay['status'] if pay else 'none'}</span>")
        actions = ""
        if lic and lic["status"] == "active":
            actions += (f"<form method='post' action='/admin/revoke' style='display:inline'>"
                        f"<input type='hidden' name='license_id' value='{lic['license_id']}'>"
                        f"<button class='btn ghost' style='padding:4px 10px;font-size:12px'>Revoke</button></form> ")
        actions += (f"<form method='post' action='/admin/grant' style='display:inline'>"
                    f"<input type='hidden' name='email' value='{u['email']}'>"
                    f"<input type='hidden' name='plan' value='annual'>"
                    f"<button class='btn ghost' style='padding:4px 10px;font-size:12px'>Re-issue</button></form>")
        admin_tag = " <span class='pill ok'>admin</span>" if u["is_admin"] else ""
        rows += (f"<tr><td>{u['email']}{admin_tag}<br><span class='muted'>{u['org'] or u['name'] or ''}</span></td>"
                 f"<td>{pay_s}</td>"
                 f"<td>{lic_s}<br><span class='muted'>{('exp '+lic['expires']) if lic else ''}</span></td>"
                 f"<td>{actions}</td></tr>")

    table = ("<div class='card'><h2>Accounts</h2>"
             "<table><tr><th>User</th><th>Payment</th><th>License</th><th>Actions</th></tr>"
             f"{rows}</table></div>")
    return summary + table
