// We import Fastify cookie here to ensure that the cookie plugin and its types are registered.
import '@fastify/cookie'

import type { FastifyRequest } from 'fastify'

import type { UserRoles } from '@types'

import type { AuthenticationConfig_Disabled } from './authentication.config'
import { zUserRoles } from './authentication.types'
import type { UserInfo } from './authentication.types'
import { parseStringList } from './utils'

type MockAuthenticationResult = {
  /**
   * UserInfo to return from the mock service.
   */
  userInfo: UserInfo

  /**
   * Roles assigned to the user.
   */
  userRoles: UserRoles

  /**
   * Access token to return from the mock service.
   */
  accessToken: string

  /**
   * URL to redirect to after authenticating.
   */
  authRedirectUrl: string

  /**
   * URL to redirect to after logging out.
   */
  logoutRedirectUrl: string
}

const mockResult: MockAuthenticationResult = {
  accessToken: '',
  authRedirectUrl: '',
  logoutRedirectUrl: '',
  userInfo: {
    sub: 'test',
    name: 'Test User',
    preferred_username: 'test',
    given_name: 'Test',
    family_name: 'User',
    email: 'test@postxl.com',
    groups: undefined,
    exp: undefined,
  },
  userRoles: ['admin'],
}

export class MockAuthenticationService {
  public readonly cookieDomain = 'localhost'
  private readonly mockResult: MockAuthenticationResult

  constructor(private readonly config: AuthenticationConfig_Disabled) {
    const parsedConfigRoles = zUserRoles.safeParse(config.mockIdentity.roles)

    this.mockResult = {
      ...mockResult,
      userInfo: {
        ...mockResult.userInfo,
        sub: config.mockIdentity.sub,
        name: config.mockIdentity.name,
        preferred_username: config.mockIdentity.email,
        given_name: config.mockIdentity.name.split(' ').at(0) ?? config.mockIdentity.name,
        family_name: config.mockIdentity.name.split(' ').slice(1).join(' ') || config.mockIdentity.name,
        email: config.mockIdentity.email,
        groups: config.mockIdentity.groups,
      },
      userRoles: parsedConfigRoles.success ? parsedConfigRoles.data : mockResult.userRoles,
    }
  }

  private parseHeaderRoles(raw: unknown): UserRoles {
    const parsed = zUserRoles.safeParse(parseStringList(raw, this.mockResult.userRoles))
    return parsed.success ? parsed.data : this.mockResult.userRoles
  }

  private getTestOverrideRoles(req: FastifyRequest | undefined): UserRoles | null {
    // Test override headers are only honored when:
    // - authentication is disabled (`AUTH=false`) and
    // - explicit test overrides are enabled (`AUTH_TEST_OVERRIDES=true`) and
    // - runtime is test (`NODE_ENV=test`)
    if (!this.config.allowTestOverrides || process.env.NODE_ENV !== 'test' || !req) {
      return null
    }
    const rawRoles = req.headers['x-pxl-test-roles']
    return this.parseHeaderRoles(rawRoles)
  }

  private getTestOverrideSub(req: FastifyRequest | undefined): string | undefined {
    if (!this.config.allowTestOverrides || process.env.NODE_ENV !== 'test' || !req) {
      return undefined
    }
    const rawSub = req.headers['x-pxl-test-sub']
    if (typeof rawSub !== 'string' || rawSub.trim().length === 0) {
      return undefined
    }
    return rawSub.trim()
  }

  public refreshAccessToken(): Promise<
    | {
        ok: true
        accessToken: string
        refreshToken?: string
        state: null
      }
    | {
        ok: false
        error: unknown
      }
  > {
    return Promise.resolve({ ok: true, accessToken: this.mockResult.accessToken, state: null }) // NOSONAR - We want to adhere to the interface
  }
  public getAccessTokenFromURL(): Promise<
    | {
        ok: true
        accessToken: string
        state: null
      }
    | {
        ok: false
        error: unknown
      }
  > {
    return Promise.resolve({ ok: true, accessToken: this.mockResult.accessToken, state: null }) // NOSONAR - We want to adhere to the interface
  }
  public getAuthenticationUrl(): Promise<string> {
    return Promise.resolve(this.mockResult.authRedirectUrl) // NOSONAR - We want to adhere to the interface
  }

  public async getUserInfoFromRequest(req?: FastifyRequest): Promise<{ ok: true; userInfo: UserInfo } | { ok: false }> {
    const info = await this.getUserInfo(req)

    return info
  }

  public getUserInfo(req?: FastifyRequest): Promise<{ ok: true; userInfo: UserInfo } | { ok: false }> {
    const overrideSub = this.getTestOverrideSub(req)
    // NOSONAR - We want to keep all validations async to adhere to the same interface
    return Promise.resolve({
      ok: true,
      userInfo: {
        ...this.mockResult.userInfo,
        sub: overrideSub ?? this.mockResult.userInfo.sub,
      },
    })
  }

  public async getUserRolesFromRequest(
    req?: FastifyRequest,
  ): Promise<{ ok: true; userRoles: UserRoles } | { ok: false }> {
    const roles = await this.getUserRoles(req)

    return roles
  }

  public getUserRoles(req?: FastifyRequest): Promise<{ ok: true; userRoles: UserRoles } | { ok: false }> {
    return Promise.resolve({
      ok: true,
      userRoles: this.getTestOverrideRoles(req) ?? this.mockResult.userRoles,
    }) // NOSONAR - We want to adhere to the interface
  }
  public getLogoutUrl(): Promise<string> {
    return Promise.resolve(this.mockResult.logoutRedirectUrl) // NOSONAR - We want to adhere to the interface
  }
}
