import { Logger } from '@nestjs/common';
import { Args, Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql';

import { AuthAction, Resource } from '@unraid/shared/graphql.model.js';
import { ApiConfig } from '@unraid/shared/services/api-config.js';
import { UserSettingsService } from '@unraid/shared/services/user-settings.js';
import { UsePermissions } from '@unraid/shared/use-permissions.directive.js';
import { GraphQLJSON } from 'graphql-scalars';

import { LifecycleService } from '@app/unraid-api/app/lifecycle.service.js';
import { Public } from '@app/unraid-api/auth/public.decorator.js';
import {
    Settings,
    UnifiedSettings,
    UpdateSettingsResponse,
} from '@app/unraid-api/graph/resolvers/settings/settings.model.js';
import { ApiSettings } from '@app/unraid-api/graph/resolvers/settings/settings.service.js';
import { SsoSettings } from '@app/unraid-api/graph/resolvers/settings/sso-settings.model.js';
import { OidcConfigPersistence } from '@app/unraid-api/graph/resolvers/sso/core/oidc-config.service.js';
import { OidcProvider } from '@app/unraid-api/graph/resolvers/sso/models/oidc-provider.model.js';

@Resolver(() => Settings)
export class SettingsResolver {
    constructor(
        private readonly apiSettings: ApiSettings,
        private readonly oidcConfig: OidcConfigPersistence
    ) {}

    @Query(() => Settings)
    async settings() {
        return {
            id: 'settings',
        };
    }

    @ResolveField(() => ApiConfig, { description: 'The API setting values' })
    async api() {
        return {
            id: 'api-settings',
            ...this.apiSettings.getSettings(),
        };
    }

    @ResolveField(() => UnifiedSettings)
    async unified() {
        return {
            id: 'unified-settings',
        };
    }

    @ResolveField(() => SsoSettings)
    async sso() {
        return {
            id: 'sso-settings',
        };
    }

    @Query(() => Boolean)
    @Public()
    public async isSSOEnabled(): Promise<boolean> {
        // Check if any OIDC provider has authorization rules configured
        const providers = await this.oidcConfig.getProviders();
        return providers.some((p) => p.authorizationRules && p.authorizationRules.length > 0);
    }
}

@Resolver(() => UnifiedSettings)
export class UnifiedSettingsResolver {
    private readonly logger = new Logger(UnifiedSettingsResolver.name);
    constructor(
        private readonly userSettings: UserSettingsService,
        private readonly lifecycleService: LifecycleService
    ) {}

    @ResolveField(() => GraphQLJSON)
    async dataSchema() {
        const { properties } = await this.userSettings.getAllSettings(['api', 'sso']);
        return {
            type: 'object',
            properties,
        };
    }

    @ResolveField(() => GraphQLJSON)
    async uiSchema() {
        const { elements } = await this.userSettings.getAllSettings(['api', 'sso']);
        return {
            type: 'VerticalLayout',
            elements,
        };
    }

    @ResolveField(() => GraphQLJSON)
    async values() {
        return this.userSettings.getAllValues();
    }

    @Mutation(() => UpdateSettingsResponse)
    @UsePermissions({
        action: AuthAction.UPDATE_ANY,
        resource: Resource.CONFIG,
    })
    async updateSettings(
        @Args('input', { type: () => GraphQLJSON }) input: Record<string, unknown>
    ): Promise<UpdateSettingsResponse> {
        this.logger.verbose('Updating Settings %O', input);
        const { restartRequired, values } = await this.userSettings.updateNamespacedValues(input);
        this.logger.verbose('Updated Setting Values %O', values);
        if (restartRequired) {
            // hack: allow time for pending writes to flush
            this.lifecycleService.restartApi({ delayMs: 300 });
        }
        return { restartRequired, values };
    }
}

@Resolver(() => SsoSettings)
export class SsoSettingsResolver {
    constructor(private readonly oidcConfig: OidcConfigPersistence) {}

    @ResolveField(() => [OidcProvider], { description: 'List of configured OIDC providers' })
    async oidcProviders(): Promise<OidcProvider[]> {
        return this.oidcConfig.getProviders();
    }
}
