import { GROUP_LOG_ACTION } from '@/lib/groups/groupLogActions'
import {
    GROUP_ROLE_MEMBER,
    isGroupDeputyRole,
    isGroupLeaderRole,
} from '@/lib/groups/groupMemberRole'
import { logGroupAction } from '@/server/groups/insertGroupLog'
import { resolveMediaUrl } from '@/lib/media/resolveMediaUrl'
import type { getDb } from '@/server/db'

type AppDb = Awaited<ReturnType<typeof getDb>>

export type GroupApplyListItem = {
    userId: string
    displayName: string
    avatar: string | null
    applicationAt: number
}

export async function isGroupMember(
    db: AppDb,
    groupId: string,
    userId: string
): Promise<boolean> {
    const row = await db.get<{ ok: number }>(
        `SELECT 1 AS ok FROM user_groups WHERE group_id = ? AND user_id = ? LIMIT 1`,
        [groupId, userId]
    )
    return Boolean(row?.ok)
}

export async function isGroupApplicationOpen(
    db: AppDb,
    groupId: string
): Promise<boolean> {
    const row = await db.get<{ isCanApplicapplication: number }>(
        `SELECT is_can_application AS isCanApplicapplication FROM groups WHERE group_id = ?`,
        [groupId]
    )
    if (!row) return false
    return Boolean(row.isCanApplicapplication)
}

export async function hasPendingGroupApply(
    db: AppDb,
    groupId: string,
    userId: string
): Promise<boolean> {
    const row = await db.get<{ ok: number }>(
        `SELECT 1 AS ok FROM group_apply WHERE group_id = ? AND user_id = ? LIMIT 1`,
        [groupId, userId]
    )
    return Boolean(row?.ok)
}

export async function listGroupApplications(
    db: AppDb,
    groupId: string
): Promise<GroupApplyListItem[]> {
    const rows = await db.all<{
        userId: string
        displayName: string
        avatar: string | null
        applicationAt: number
    }>(
        `SELECT ga.user_id AS userId, u.display_name AS displayName, u.avatar_url AS avatar,
                ga.application_at AS applicationAt
         FROM group_apply ga
         INNER JOIN users u ON u.user_id = ga.user_id
         WHERE ga.group_id = ?
         ORDER BY ga.application_at ASC`,
        [groupId]
    )
    return (rows ?? []).map((r) => ({
        userId: r.userId,
        displayName: r.displayName,
        avatar: resolveMediaUrl(r.avatar) || null,
        applicationAt: r.applicationAt ?? 0,
    }))
}

export async function createGroupApply(
    db: AppDb,
    groupId: string,
    userId: string
): Promise<{ applicationAt: number; created: boolean }> {
    if (!(await isGroupApplicationOpen(db, groupId))) {
        throw new GroupApplyError('APPLICATION_CLOSED', 'Applications are not open for this family')
    }
    if (await isGroupMember(db, groupId, userId)) {
        throw new GroupApplyError('ALREADY_MEMBER', 'Already a member of this family')
    }

    const existing = await db.get<{ applicationAt: number }>(
        `SELECT application_at AS applicationAt FROM group_apply WHERE group_id = ? AND user_id = ?`,
        [groupId, userId]
    )
    if (existing) {
        return { applicationAt: existing.applicationAt ?? 0, created: false }
    }

    await db.run(
        `INSERT INTO group_apply (group_id, user_id) VALUES (?, ?)`,
        [groupId, userId]
    )
    const row = await db.get<{ applicationAt: number }>(
        `SELECT application_at AS applicationAt FROM group_apply WHERE group_id = ? AND user_id = ?`,
        [groupId, userId]
    )
    const applicationAt = row?.applicationAt ?? Math.floor(Date.now() / 1000)
    logGroupAction(db, {
        groupId,
        userId,
        targetId: userId,
        actionContent: GROUP_LOG_ACTION.MEMBER_APPLIED,
        operationAt: applicationAt,
    })
    return { applicationAt, created: true }
}

export async function deleteGroupApply(
    db: AppDb,
    groupId: string,
    userId: string,
    options?: { logCancel?: boolean }
): Promise<boolean> {
    const result = await db.run(
        `DELETE FROM group_apply WHERE group_id = ? AND user_id = ?`,
        [groupId, userId]
    )
    const removed = (result.changes ?? 0) > 0
    if (removed && options?.logCancel) {
        logGroupAction(db, {
            groupId,
            userId,
            targetId: userId,
            actionContent: GROUP_LOG_ACTION.MEMBER_APPLY_CANCELLED,
        })
    }
    return removed
}

export async function approveGroupApply(
    db: AppDb,
    groupId: string,
    userId: string,
    reviewerUserId: string
): Promise<void> {
    const pending = await db.get<{ ok: number }>(
        `SELECT 1 AS ok FROM group_apply WHERE group_id = ? AND user_id = ? LIMIT 1`,
        [groupId, userId]
    )
    if (!pending?.ok) {
        throw new GroupApplyError('NOT_FOUND', 'Application not found')
    }
    if (await isGroupMember(db, groupId, userId)) {
        await deleteGroupApply(db, groupId, userId)
        throw new GroupApplyError('ALREADY_MEMBER', 'User is already a member')
    }

    await db.exec(`BEGIN`)
    try {
        await db.run(
            `INSERT INTO user_groups (user_id, group_id, role) VALUES (?, ?, ?)`,
            [userId, groupId, GROUP_ROLE_MEMBER]
        )
        await db.run(`DELETE FROM group_apply WHERE group_id = ? AND user_id = ?`, [
            groupId,
            userId,
        ])
        await db.exec(`COMMIT`)
        logGroupAction(db, {
            groupId,
            userId: reviewerUserId,
            targetId: userId,
            actionContent: GROUP_LOG_ACTION.MEMBER_JOIN_APPROVED,
        })
    } catch (e) {
        try {
            await db.exec(`ROLLBACK`)
        } catch {
            // ignore
        }
        throw e
    }
}

export async function rejectGroupApply(
    db: AppDb,
    groupId: string,
    userId: string,
    reviewerUserId: string
): Promise<boolean> {
    const removed = await deleteGroupApply(db, groupId, userId)
    if (removed) {
        logGroupAction(db, {
            groupId,
            userId: reviewerUserId,
            targetId: userId,
            actionContent: GROUP_LOG_ACTION.MEMBER_APPLY_REJECTED,
        })
    }
    return removed
}

export function canReviewGroupApplications(role: string | null | undefined): boolean {
    return isGroupLeaderRole(role) || isGroupDeputyRole(role)
}

export async function getGroupApplyContext(
    db: AppDb,
    groupId: string,
    viewerUserId: string | null
): Promise<{
    isCanApplicapplication: boolean
    viewerApplyPending: boolean
    pendingApplications: GroupApplyListItem[]
    canReviewApplications: boolean
}> {
    const isCanApplicapplication = await isGroupApplicationOpen(db, groupId)

    if (!viewerUserId) {
        return {
            isCanApplicapplication,
            viewerApplyPending: false,
            pendingApplications: [],
            canReviewApplications: false,
        }
    }

    const membership = await db.get<{ role: string }>(
        `SELECT role FROM user_groups WHERE group_id = ? AND user_id = ?`,
        [groupId, viewerUserId]
    )

    if (membership) {
        const canReview = canReviewGroupApplications(membership.role)
        const pendingApplications = canReview ? await listGroupApplications(db, groupId) : []
        return {
            isCanApplicapplication,
            viewerApplyPending: false,
            pendingApplications,
            canReviewApplications: canReview,
        }
    }

    const viewerApplyPending = await hasPendingGroupApply(db, groupId, viewerUserId)
    return {
        isCanApplicapplication,
        viewerApplyPending,
        pendingApplications: [],
        canReviewApplications: false,
    }
}

export class GroupApplyError extends Error {
    code: string

    constructor(code: string, message: string) {
        super(message)
        this.code = code
        this.name = 'GroupApplyError'
    }
}
