import axios, { AxiosRequestConfig } from 'axios'

import { Authenticator } from './authenticator'
import { RESTClient, RESTClientConfig } from './rest-client'
import { AppsEndpoint } from './endpoint/apps'
import { CapabilitiesEndpoint } from './endpoint/capabilities'
import { DevicePreferencesEndpoint } from './endpoint/devicepreferences'
import { DeviceProfilesEndpoint } from './endpoint/deviceprofiles'
import { ChannelsEndpoint } from './endpoint/channels'
import { DevicesEndpoint } from './endpoint/devices'
import { DriversEndpoint } from './endpoint/drivers'
import { HistoryEndpoint } from './endpoint/history'
import { HubdevicesEndpoint } from './endpoint/hubdevices'
import { InstalledAppsEndpoint } from './endpoint/installedapps'
import { ModesEndpoint } from './endpoint/modes'
import { LocationsEndpoint } from './endpoint/locations'
import { NotificationsEndpoint } from './endpoint/notifications'
import { OrganizationsEndpoint } from './endpoint/organizations'
import { PresentationEndpoint } from './endpoint/presentation'
import { RoomsEndpoint } from './endpoint/rooms'
import { RulesEndpoint } from './endpoint/rules'
import { ScenesEndpoint } from './endpoint/scenes'
import { SubscriptionsEndpoint } from './endpoint/subscriptions'
import { SchedulesEndpoint } from './endpoint/schedules'
import { SchemaEndpoint } from './endpoint/schema'
import { ServicesEndpoint } from './endpoint/services'
import { VirtualDevicesEndpoint } from './endpoint/virtualdevices'
import { SmartThingsURLProvider, defaultSmartThingsURLProvider, HttpClientHeaders } from './endpoint-client'


export class SmartThingsClient extends RESTClient {
	public readonly apps: AppsEndpoint
	public readonly capabilities: CapabilitiesEndpoint
	public readonly channels: ChannelsEndpoint
	public readonly devicePreferences: DevicePreferencesEndpoint
	public readonly deviceProfiles: DeviceProfilesEndpoint
	public readonly devices: DevicesEndpoint
	public readonly drivers: DriversEndpoint
	public readonly history: HistoryEndpoint
	public readonly hubdevices: HubdevicesEndpoint
	public readonly installedApps: InstalledAppsEndpoint
	public readonly modes: ModesEndpoint
	public readonly notifications: NotificationsEndpoint
	public readonly organizations: OrganizationsEndpoint
	public readonly locations: LocationsEndpoint
	public readonly presentation: PresentationEndpoint
	public readonly rooms: RoomsEndpoint
	public readonly rules: RulesEndpoint
	public readonly scenes: ScenesEndpoint
	public readonly subscriptions: SubscriptionsEndpoint
	public readonly schedules: SchedulesEndpoint
	public readonly schema: SchemaEndpoint
	public readonly services: ServicesEndpoint
	public readonly virtualDevices: VirtualDevicesEndpoint

	constructor(authenticator: Authenticator, config?: RESTClientConfig) {
		super(authenticator, config)

		this.apps = new AppsEndpoint(this.config)
		this.capabilities = new CapabilitiesEndpoint(this.config)
		this.channels = new ChannelsEndpoint(this.config)
		this.devicePreferences = new DevicePreferencesEndpoint(this.config)
		this.deviceProfiles = new DeviceProfilesEndpoint(this.config)
		this.devices = new DevicesEndpoint(this.config)
		this.drivers = new DriversEndpoint(this.config)
		this.history = new HistoryEndpoint(this.config)
		this.hubdevices = new HubdevicesEndpoint(this.config)
		this.installedApps = new InstalledAppsEndpoint(this.config)
		this.locations = new LocationsEndpoint(this.config)
		this.modes = new ModesEndpoint(this.config)
		this.notifications = new NotificationsEndpoint(this.config)
		this.organizations = new OrganizationsEndpoint(this.config)
		this.presentation = new PresentationEndpoint(this.config)
		this.rooms = new RoomsEndpoint(this.config)
		this.rules = new RulesEndpoint(this.config)
		this.scenes = new ScenesEndpoint(this.config)
		this.subscriptions = new SubscriptionsEndpoint(this.config)
		this.schedules = new SchedulesEndpoint(this.config)
		this.schema = new SchemaEndpoint(this.config)
		this.services = new ServicesEndpoint(this.config)
		this.virtualDevices = new VirtualDevicesEndpoint(this.config)
	}

	public setLocation(id: string): SmartThingsClient {
		this.config.locationId = id
		return this
	}

	/**
	 * @param headers http headers to be merged with existing client headers
	 */
	public clone(headers?: HttpClientHeaders): SmartThingsClient {
		const config: RESTClientConfig = { ...this.config, headers: { ...this.config.headers, ...headers } }
		return new SmartThingsClient(this.config.authenticator, config)
	}
}

export class SmartThingsOAuthClient {
	private authURL: string

	constructor(private clientId: string, private clientSecret: string,
			private redirectUri: string, urlProvider?: SmartThingsURLProvider) {
		this.authURL = urlProvider?.authURL || defaultSmartThingsURLProvider.authURL
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	public async redeemCode(authCode: string): Promise<any> {
		const headers = {
			'Content-Type': 'application/x-www-form-urlencoded',
			'Authorization': `Basic ${Buffer.from(this.clientId + ':' + this.clientSecret).toString('base64')}`,
			'Accept': 'application/json',
		}

		const axiosConfig: AxiosRequestConfig = {
			url: this.authURL,
			method: 'POST',
			headers,
			data: `grant_type=authorization_code&code=${authCode}&client_id=${this.clientId}&redirect_uri=${this.redirectUri}`,
		}

		const response = await axios.request(axiosConfig)
		if (response.status > 199 && response.status < 300) {
			return response.data
		}
		throw Error(`error ${response.status} with message ${response.data}`)
	}
}