# DATABASE LOGIC
import json
import sqlite3

from _sqlite3 import Error
from dataclasses import dataclass
from datetime import timedelta
from logging import Filter

from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Filter, Kind
from nostr_dvm.utils.nostr_utils import send_event


@dataclass
class User:
    npub: str
    balance: int
    iswhitelisted: bool
    isblacklisted: bool
    name: str
    nip05: str
    lud16: str
    lastactive: int
    subscribed: int


def create_sql_table(db):
    try:
        import os
        if not os.path.exists(r'db'):
            os.makedirs(r'db')
        if not os.path.exists(r'outputs'):
            os.makedirs(r'outputs')
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute(""" CREATE TABLE IF NOT EXISTS users (
                                            npub text PRIMARY KEY,
                                            sats integer NOT NULL,
                                            iswhitelisted boolean,
                                            isblacklisted boolean,
                                            nip05 text,
                                            lud16 text,
                                            name text,
                                            lastactive integer,
                                            subscribed integer
                                        ); """)
        cur.execute("SELECT name FROM sqlite_master")
        con.close()

    except Error as e:
        print(e)


def add_sql_table_column(db):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute(""" ALTER TABLE users ADD COLUMN subscribed 'integer' """)
        con.close()
    except Error as e:
        print(e)


def add_to_sql_table(db, npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        data = (npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed)
        cur.execute("INSERT or IGNORE INTO users VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", data)
        con.commit()
        con.close()
    except Error as e:
        print("Error when Adding to DB: " + str(e))


def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        data = (balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed, npub)

        cur.execute(""" UPDATE users
                  SET sats = ? ,
                      iswhitelisted = ? ,
                      isblacklisted = ? ,
                      nip05 = ? ,
                      lud16 = ? ,
                      name = ? ,
                      lastactive = ?,
                      subscribed = ?
                  WHERE npub = ?""", data)
        con.commit()
        con.close()
    except Error as e:
        print("Error Updating DB: " + str(e))


def get_from_sql_table(db, npub):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute("SELECT * FROM users WHERE npub=?", (npub,))
        row = cur.fetchone()
        con.close()
        if row is None:
            return None
        else:

            if len(row) < 9:
                add_sql_table_column(db)
                # Migrate 

            user = User
            user.npub = row[0]
            user.balance = row[1]
            user.iswhitelisted = row[2]
            user.isblacklisted = row[3]
            user.nip05 = row[4]
            user.lud16 = row[5]
            user.name = row[6]
            user.lastactive = row[7]
            user.subscribed = row[8]
            if user.subscribed is None:
                user.subscribed = 0

            return user

    except Error as e:
        print("Error Getting from DB: " + str(e))


def delete_from_sql_table(db, npub):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute("DELETE FROM users WHERE npub=?", (npub,))
        con.commit()
        con.close()
    except Error as e:
        print(e)


def clean_db(db):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute("SELECT * FROM users WHERE npub IS NULL OR npub = '' ")
        rows = cur.fetchall()
        for row in rows:
            print(row)
            delete_from_sql_table(db, row[0])
        con.close()
        return rows
    except Error as e:
        print(e)


def list_db(db):
    try:
        con = sqlite3.connect(db)
        cur = con.cursor()
        cur.execute("SELECT * FROM users ORDER BY sats DESC")
        rows = cur.fetchall()
        for row in rows:
            print(row)
        con.close()
    except Error as e:
        print(e)


async def update_user_balance(db, npub, additional_sats, client, config, giftwrap=False):
    user = get_from_sql_table(db, npub)
    if user is None:
        name, nip05, lud16 = fetch_user_metadata(npub, client)
        add_to_sql_table(db, npub, (int(additional_sats) + config.NEW_USER_BALANCE), False, False,
                         nip05, lud16, name, Timestamp.now().as_secs(), 0)
        print("Adding User: " + npub + " (" + npub + ")")
    else:
        user = get_from_sql_table(db, npub)
        new_balance = int(user.balance) + int(additional_sats)
        update_sql_table(db, npub, new_balance, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
                         user.name,
                         Timestamp.now().as_secs(), user.subscribed)
        print("Updated user balance for: " + str(user.name) +
              " Zap amount: " + str(additional_sats) + " Sats. New balance: " + str(new_balance) + " Sats")

        if config is not None:
            keys = Keys.parse(config.PRIVATE_KEY)
            # time.sleep(1.0)

            message = ("Added " + str(additional_sats) + " Sats to balance. New balance is " + str(
                new_balance) + " Sats.")

            if giftwrap:
                await client.send_private_msg(PublicKey.parse(npub), message, None)
            else:
                evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.parse(npub), message,
                                                        None).to_event(keys)
                await send_event(evt, client=client, dvm_config=config)


def update_user_subscription(npub, subscribed_until, client, dvm_config):
    user = get_from_sql_table(dvm_config.DB, npub)
    if user is None:
        name, nip05, lud16 = fetch_user_metadata(npub, client)
        add_to_sql_table(dvm_config.DB, npub, dvm_config.NEW_USER_BALANCE, False, False,
                         nip05, lud16, name, Timestamp.now().as_secs(), 0)
        print("Adding User: " + npub + " (" + npub + ")")
    else:
        user = get_from_sql_table(dvm_config.DB, npub)

        update_sql_table(dvm_config.DB, npub, user.balance, user.iswhitelisted, user.isblacklisted, user.nip05,
                         user.lud16,
                         user.name,
                         Timestamp.now().as_secs(), subscribed_until)
        print("Updated user subscription for: " + str(user.name))


async def get_or_add_user(db, npub, client, config, update=False, skip_meta=False):
    user = get_from_sql_table(db, npub)
    if user is None:
        try:
            if skip_meta:
                name = npub
                nip05 = ""
                lud16 = ""
            else:
                name, nip05, lud16 = await fetch_user_metadata(npub, client)
            print("Adding User: " + npub + " (" + npub + ")")
            add_to_sql_table(db, npub, config.NEW_USER_BALANCE, False, False, nip05,
                             lud16, name, Timestamp.now().as_secs(), 0)
            user = get_from_sql_table(db, npub)
            return user
        except Exception as e:
            print("Error Adding User to DB: " + str(e))
    elif update:
        try:
            name, nip05, lud16 = await fetch_user_metadata(npub, client)
            print("Updating User: " + npub + " (" + npub + ")")
            update_sql_table(db, user.npub, user.balance, user.iswhitelisted, user.isblacklisted, nip05,
                             lud16, name, Timestamp.now().as_secs(), user.subscribed)
            user = get_from_sql_table(db, npub)
            return user
        except Exception as e:
            print("Error Updating User in DB: " + str(e))

    return user


async def fetch_user_metadata(npub, client):
    name = ""
    nip05 = ""
    lud16 = ""
    pk = PublicKey.parse(npub)
    print(f"\nGetting profile metadata for {pk.to_bech32()}...")
    profile_filter = Filter().kind(Kind(0)).author(pk).limit(1)
    events = await client.get_events_of([profile_filter], timedelta(seconds=1))
    if len(events) > 0:
        latest_entry = events[0]
        latest_time = 0
        try:
            for entry in events:
                if entry.created_at().as_secs() > latest_time:
                    latest_time = entry.created_at().as_secs()
                    latest_entry = entry
            profile = json.loads(latest_entry.content())
            if profile.get("name"):
                name = profile['name']
            if profile.get("nip05"):
                nip05 = profile['nip05']
            if profile.get("lud16"):
                lud16 = profile['lud16']
        except Exception as e:
            print(e)
    return name, nip05, lud16
