// Copied from https://github.com/atinux/nuxthub-better-auth

import { defu } from 'defu'
import { createAuthClient } from 'better-auth/vue'
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
import type { RouteLocationRaw } from 'vue-router'
import type { RouteLocationNormalizedLoaded } from '#vue-router'
import {
  accessControl,
  employerRole,
  worksCouncilMemberRole,
  employeeRole,
  adminRole,
  ownerRole,
  ROLES
} from '~/server/utils/permissions'

interface RuntimeAuthConfig {
  redirectUserTo: RouteLocationRaw | string
  redirectGuestTo: RouteLocationRaw | string
}

// Types can be found here: https://github.com/better-auth/better-auth/blob/3f574ec70bb15c155a78673d42c5e25f7376ced3/packages/better-auth/src/plugins/organization/routes/crud-invites.ts#L531
type Client = ReturnType<typeof useAuth>['client']

export type Session = Client['$Infer']['Session']
export type User = Session['user']
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
export type Organization = Client['$Infer']['Organization']
export type Invitation = Client['$Infer']['Invitation']

// Extended invitation type with additional organization and inviter details
export type CustomInvitation =
  | (Invitation & {
      organizationName: string
      organizationSlug: string
      inviterEmail: string
    })
  | null

// TODO: Move into pinia store
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
const sessionFetching = import.meta.server ? ref(false) : ref(false)
const jwt = ref<string | null>(null)
const organizations = ref<
  { id: string; name: string; createdAt: Date; slug: string; metadata?: any; logo?: string | null }[]
>([])
const selectedOrganization = ref<{
  id: string
  name: string
  createdAt: Date
  slug: string
  metadata?: any
  logo?: string | null
} | null>(null)
const activeMember = ref<{ role: string } | null>(null)

export function useAuth() {
  const url = useRequestURL()
  const route = useRoute()
  const headers = import.meta.server ? useRequestHeaders() : undefined

  const client = createAuthClient({
    baseURL: url.origin,
    fetchOptions: {
      headers
    },
    user: {
      deleteUser: {
        enabled: true
      }
    },
    plugins: [
      organizationClient({
        // Pass the same access control instance and roles to client
        ac: accessControl,
        roles: {
          [ROLES.EMPLOYER]: employerRole,
          [ROLES.WORKS_COUNCIL_MEMBER]: worksCouncilMemberRole,
          [ROLES.EMPLOYEE]: employeeRole,
          [ROLES.ADMIN]: adminRole,
          [ROLES.OWNER]: ownerRole
        }
      }),
      jwtClient()
    ]
  })

  const options = defu(useRuntimeConfig().public.auth as Partial<RuntimeAuthConfig>, {
    redirectUserTo: '/',
    redirectGuestTo: '/login'
  })

  async function fetchSession(targetPath?: string) {
    if (sessionFetching.value) {
      console.log('already fetching session')
      return
    }
    sessionFetching.value = true
    const { data } = await client.getSession({
      fetchOptions: {
        headers
      }
    })
    session.value = data?.session || null
    user.value = data?.user || null
    sessionFetching.value = false

    // Only fetch JWT and organizations if we have a session and not on public routes
    if (session.value && !isPublicPath(targetPath)) {
      await fetchJwtAndOrganizations()
    }

    return data
  }

  async function fetchJwtAndOrganizations() {
    // Fetch JWT
    const tokenResult = await client.token()
    jwt.value = tokenResult.data?.token ?? null

    // Fetch organization
    const orgResult = await client.organization.list({
      fetchOptions: {
        headers
      }
    })
    organizations.value = orgResult.data ?? []

    if (!selectedOrganization.value && organizations.value.length > 0) {
      selectedOrganization.value = organizations.value[0]
    }

    // Fetch active member
    const activeMemberResult = await client.organization.getActiveMember({
      fetchOptions: {
        headers
      }
    })
    activeMember.value = activeMemberResult.data || null
  }

  watch(
    () => selectedOrganization.value,
    async (newValue) => {
      if (newValue) {
        await client.organization.setActive({
          organizationId: newValue?.id
        })
      }
    }
  )

  if (import.meta.client) {
    client.$store.listen('$sessionSignal', async (signal) => {
      if (!signal) return
      await fetchSession()
    })
  }

  function isPublicPath(path?: string) {
    const finalPath = path ?? route.path
    const publicRoutes = ['/login', '/signup', '/accept-invitation']
    return publicRoutes.some((path) => finalPath.startsWith(path))
  }

  async function signOut({ redirectTo }: { redirectTo?: RouteLocationRaw } = {}) {
    const res = await client.signOut()
    if (res.error) {
      console.error('Error signing out:', res.error)
      return res
    }
    session.value = null
    user.value = null
    if (redirectTo) {
      await navigateTo(redirectTo, { external: true })
    }
    return res
  }

  return {
    session,
    user,
    loggedIn: computed(() => !!session.value),
    signIn: client.signIn,
    signUp: client.signUp,
    deleteUser: client.deleteUser,
    signOut,
    organization: client.organization,
    organizations,
    selectedOrganization,
    options,
    fetchSession,
    client,
    jwt,
    isPublicPath,
    activeMember
  }
}
