import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import EmailProvider from "next-auth/providers/email";
import GithubProvider from "next-auth/providers/github";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/db/prisma";
import { findKind0Fields } from "@/utils/nostr";
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { bytesToHex } from '@noble/hashes/utils';
import { updateUser, getUserByPubkey, createUser, getUserById, getUserByEmail } from "@/db/models/userModels";
import { createRole } from "@/db/models/roleModels";
import appConfig from "@/config/appConfig";
import NDK from "@nostr-dev-kit/ndk";
import { nip19 } from 'nostr-tools';

// Initialize NDK for Nostr interactions
const ndk = new NDK({
    explicitRelayUrls: appConfig.defaultRelayUrls
});

/**
 * Handles Nostr profile synchronization and user creation/update
 * @param {string} pubkey - User's public key
 * @returns {Promise<Object|null>} User object or null if failed
 */
const syncNostrProfile = async (pubkey) => {
    await ndk.connect();
    const user = ndk.getUser({ pubkey });

    try {
        const profile = await user.fetchProfile();
        const fields = await findKind0Fields(profile);
        let dbUser = await getUserByPubkey(pubkey);

        if (dbUser) {
            // Update existing user if any of the kind0 fields differ
            if (fields.avatar !== dbUser.avatar || 
                fields.username !== dbUser.username ||
                fields.lud16 !== dbUser.lud16 ||
                fields.nip05 !== dbUser.nip05) {
                
                const updates = {
                    ...(fields.avatar !== dbUser.avatar && { avatar: fields.avatar }),
                    ...(fields.username !== dbUser.username && { 
                        username: fields.username
                    }),
                    ...(fields.lud16 !== dbUser.lud16 && { lud16: fields.lud16 }),
                    ...(fields.nip05 !== dbUser.nip05 && { nip05: fields.nip05 })
                };
                await updateUser(dbUser.id, updates);
                dbUser = await getUserByPubkey(pubkey);
            }

            // Check if user should have author role but doesn't
            if (appConfig.authorPubkeys.includes(pubkey) && !dbUser.role?.admin) {
                const role = await createRole({
                    userId: dbUser.id,
                    admin: true,
                    subscribed: false,
                });
                
                if (role) {
                    await updateUser(dbUser.id, { role: role.id });
                    dbUser = await getUserByPubkey(pubkey);
                }
            }
        } else {
            // Create new user
            const username = fields.username || pubkey.slice(0, 8);
            const lud16 = fields.lud16 || null;
            const nip05 = fields.nip05 || null;
            const payload = { 
                pubkey, 
                username, 
                avatar: fields.avatar, 
                lud16,
                nip05
            };

            dbUser = await createUser(payload);

            // Create author role if applicable
            if (appConfig.authorPubkeys.includes(pubkey)) {
                const role = await createRole({
                    userId: dbUser.id,
                    admin: true,
                    subscribed: false,
                });
                
                if (role) {
                    await updateUser(dbUser.id, { role: role.id });
                    dbUser = await getUserByPubkey(pubkey);
                }
            }
        }

        return { ...dbUser, kind0: fields };
    } catch (error) {
        console.error("Nostr profile sync error:", error);
        return null;
    }
};

/**
 * Generates an ephemeral keypair for non-Nostr login methods
 * @returns {Object} Object containing public and private keys
 */
const generateEphemeralKeypair = () => {
    const privkey = generateSecretKey();
    const pubkey = getPublicKey(privkey);
    // pubkey is hex, privkey is bytes need to convert to hex
    return {
        pubkey,
        privkey: bytesToHex(privkey)
    };
};

export const authOptions = {
    adapter: PrismaAdapter(prisma),
    providers: [
        // Nostr login provider
        CredentialsProvider({
            id: "nostr",
            name: "Nostr",
            credentials: {
                pubkey: { label: "Public Key", type: "text" }
            },
            authorize: async (credentials) => {
                if (!credentials?.pubkey) return null;
                return await syncNostrProfile(credentials.pubkey);
            }
        }),

        // Anonymous login provider
        CredentialsProvider({
            id: "anonymous",
            name: "Anonymous",
            credentials: {
                pubkey: { label: "Public Key", type: "text" },
                privkey: { label: "Private Key", type: "text" }
            },
            authorize: async (credentials) => {
                const keys = (credentials?.pubkey && credentials?.pubkey !== 'null' && credentials?.privkey && credentials?.privkey !== 'null') ? 
                    { pubkey: credentials.pubkey, privkey: credentials.privkey } : 
                    generateEphemeralKeypair();

                let user = await getUserByPubkey(keys.pubkey);
                if (!user) {
                    user = await createUser({
                        ...keys,
                        username: `anon-${keys.pubkey.slice(0, 8)}`
                    });
                }
                return { ...user, privkey: keys.privkey };
            }
        }),

        // Email provider with simpler configuration
        EmailProvider({
            server: {
                host: process.env.EMAIL_SERVER_HOST,
                port: process.env.EMAIL_SERVER_PORT,
                auth: {
                    user: process.env.EMAIL_SERVER_USER,
                    pass: process.env.EMAIL_SERVER_PASSWORD
                }
            },
            from: process.env.EMAIL_FROM
        }),

        // Github provider with ephemeral keypair generation
        GithubProvider({
            clientId: process.env.GITHUB_CLIENT_ID,
            clientSecret: process.env.GITHUB_CLIENT_SECRET,
            profile: async (profile) => {
                const keys = generateEphemeralKeypair();
                return {
                    id: profile.id.toString(),
                    pubkey: keys.pubkey,
                    privkey: keys.privkey,
                    username: profile.login,
                    email: profile.email,
                    avatar: profile.avatar_url
                };
            }
        }),
        // Recovery provider
        CredentialsProvider({
            id: "recovery",
            name: "Recovery",
            credentials: {
                nsec: { label: "Recovery Key (nsec or hex)", type: "text" }
            },
            authorize: async (credentials) => {
                if (!credentials?.nsec) return null;
                
                try {
                    // Convert nsec to hex if needed
                    let privkeyHex = credentials.nsec;
                    if (credentials.nsec.startsWith('nsec')) {
                        try {
                            const { data: decodedPrivkey } = nip19.decode(credentials.nsec);
                            privkeyHex = Buffer.from(decodedPrivkey).toString('hex');
                        } catch (error) {
                            console.error("Invalid nsec format:", error);
                            return null;
                        }
                    }

                    // Find user with matching privkey
                    const user = await prisma.user.findFirst({
                        where: { privkey: privkeyHex },
                        include: {
                            role: true,
                            purchased: true,
                            userCourses: true,
                            userLessons: true,
                            platformNip05: true,
                            platformLightningAddress: true,
                            userBadges: true
                        }
                    });

                    if (!user) {
                        console.error("No user found with provided recovery key");
                        return null;
                    }

                    return user;
                } catch (error) {
                    console.error("Recovery authorization error:", error);
                    return null;
                }
            }
        })
    ],
    callbacks: {
        // Move email handling to the signIn callback
        async signIn({ user, account }) {
            // Only handle email provider sign ins
            if (account?.provider === "email") {
                try {
                    // Check if this is an existing user
                    const existingUser = await getUserByEmail(user.email);
                    
                    if (!existingUser && user) {
                        // First time login: generate keypair
                        const keys = generateEphemeralKeypair();

                        const newUser = {
                            pubkey: keys.pubkey,
                            privkey: keys.privkey,
                            username: user.email.split('@')[0],
                            email: user.email,
                            avatar: user.image,
                        }
                        
                        // Update the user with the new keypair
                        const createdUser = await createUser(newUser);
                        return createdUser;
                    } else {
                        console.log("User already exists", existingUser);
                    }
                    
                    return true;
                } catch (error) {
                    console.error("Email sign in error:", error);
                    return false;
                }
            }
            
            return true; // Allow other provider sign ins
        },
        async session({ session, user, token }) {
            const userData = token.user || user;
            
            if (userData) {
                const fullUser = await getUserById(userData.id);
                // Convert BigInt values to strings if they exist
                if (fullUser.platformLightningAddress) {
                    fullUser.platformLightningAddress = {
                        ...fullUser.platformLightningAddress,
                        maxSendable: fullUser.platformLightningAddress.maxSendable?.toString(),
                        minSendable: fullUser.platformLightningAddress.minSendable?.toString()
                    };
                }

                // Get the user's GitHub account if it exists
                const githubAccount = await prisma.account.findFirst({
                    where: {
                        userId: fullUser.id,
                        provider: 'github'
                    }
                });
                
                session.user = {
                    ...session.user,
                    id: fullUser.id,
                    pubkey: fullUser.pubkey,
                    privkey: fullUser.privkey,
                    role: fullUser.role,
                    username: fullUser.username,
                    avatar: fullUser.avatar,
                    email: fullUser.email,
                    userCourses: fullUser.userCourses,
                    userLessons: fullUser.userLessons,
                    purchased: fullUser.purchased,
                    nip05: fullUser.nip05,
                    lud16: fullUser.lud16,
                    platformNip05: fullUser.platformNip05,
                    platformLightningAddress: fullUser.platformLightningAddress,
                    githubUsername: token.githubUsername,
                    createdAt: fullUser.createdAt,
                    userBadges: fullUser.userBadges
                };

                // Add GitHub account info to session if it exists
                if (githubAccount) {
                    session.account = {
                        provider: githubAccount.provider,
                        type: githubAccount.type,
                        providerAccountId: githubAccount.providerAccountId,
                        access_token: githubAccount.access_token,
                        token_type: githubAccount.token_type,
                        scope: githubAccount.scope,
                    };
                }
            }
            return session;
        },
        async jwt({ token, user, account, profile, session }) {
            // Convert BigInt values to strings if they exist
            if (user?.platformLightningAddress) {
                user.platformLightningAddress = {
                    ...user.platformLightningAddress,
                    maxSendable: user.platformLightningAddress.maxSendable?.toString(),
                    minSendable: user.platformLightningAddress.minSendable?.toString()
                };
            }

            // If we are linking a github account to an existing email or anon account (we have privkey)
            if (account?.provider === "github" && user?.id && user?.pubkey && user?.privkey) {
                try {
                    // First update the user's profile with GitHub info
                    const updatedUser = await updateUser(user.id, {
                        username: profile?.login || profile?.name,
                        avatar: profile?.avatar_url,
                    });

                    // Get the updated user
                    const existingUser = await getUserById(updatedUser?.id);
                    if (existingUser) {
                        token.user = existingUser;
                    }

                    // add github username to token
                    token.githubUsername = profile?.login || profile?.name;
                } catch (error) {
                    console.error("Error linking GitHub account:", error);
                }
            }

            // nostr login (we have no privkey)
            if (account?.provider === "github" && user?.id && user?.pubkey) {
                try {
                    // First check if there's already a GitHub account linked
                    const existingGithubAccount = await prisma.account.findFirst({
                        where: {
                            userId: user.id,
                            provider: 'github'
                        }
                    });

                    // add github username to token
                    token.githubUsername = profile?.login || profile?.name;

                    if (!existingGithubAccount) {
                        // Update user profile with GitHub info
                        const updatedUser = await updateUser(user.id, {
                            username: profile?.login || profile?.name,
                            avatar: profile?.avatar_url,
                            email: profile?.email // Add email if user wants it
                        });

                        // Create the GitHub account link
                        await prisma.account.create({
                            data: {
                                userId: user.id,
                                type: account.type,
                                provider: account.provider,
                                providerAccountId: account.providerAccountId,
                                access_token: account.access_token,
                                token_type: account.token_type,
                                scope: account.scope
                            }
                        });

                        // Get the updated user
                        const existingUser = await getUserById(updatedUser?.id);
                        if (existingUser) {
                            token.user = existingUser;
                        }
                    }
                } catch (error) {
                    console.error("Error linking GitHub account:", error);
                }
            }

            if (user) {
                token.user = user;
            }
            if (account) {
                token.account = account;
            }
            return token;
        }
    },
    session: {
        strategy: 'jwt',
        maxAge: 30 * 24 * 60 * 60, // 30 days
    },
    debug: process.env.NODE_ENV === 'development',
};

export default NextAuth(authOptions);
