import {Injectable} from '@angular/core';
import {ApiService} from './api.service';
import {Observable} from 'rxjs';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import ApiResponse from '../structures/ApiResponse';
import {environment} from '../../environments/environment';
import {debug} from '../utility';
import * as Structures from '../structures/db-api';

@Injectable({
    providedIn: 'root',
})
export class DbApiService {
    protected token?: string;
    protected tokenTime?: number;
    protected databaseBase: string = environment.databaseBase;

    constructor(
        protected readonly api: ApiService,
        protected readonly http: HttpClient,
    ) {
        Structures._setDbApi(this);
    }

    public async getDatabase(databaseId: string): Promise<Structures.Database> {
        return new Promise(async (res, rej) => {

            this._authRequest(`/${databaseId}`).subscribe({
                next: async response => {
                    if ( response.status !== 200 ) {
                        debug('Invalid get database response.', response);
                        return rej(new Error('Invalid database ID.'));
                    }

                    return res(new Structures.Database(response.data));
                },
                error: rej,
            });
        });
    }

    public async getDatabases(): Promise<Structures.Database[]> {
        return new Promise(async (res, rej) => {

            this._authRequest('/').subscribe({
                next: async response => {
                    return res(response.data.map(x => new Structures.Database(x)));
                },
                error: rej,
            });
        });
    }

    public async getColumns(databaseId: string): Promise<Structures.DatabaseColumn[]> {
        return new Promise(async (res, rej) => {

            this._authRequest(`${databaseId}/columns`).subscribe({
                next: async response => {
                    return res(response.data.map(x => new Structures.DatabaseColumn(x)));
                },
                error: rej,
            });
        });
    }

    public async getColumnOrder(databaseId: string): Promise<string[]> {
        return new Promise(async (res, rej) => {
            this._authRequest(`${databaseId}/columns/order`).subscribe({
                next: async response => {
                    return res(response.data);
                },
                error: rej,
            });
        });
    }

    public async getData(databaseId: string): Promise<Structures.DatabaseRecord[]> {
        return new Promise(async (res, rej) => {
            this._authRequest(`${databaseId}/data`).subscribe({
                next: async response => {
                    return res(response.data.map(x => new Structures.DatabaseRecord(x)));
                },
                error: rej,
            });
        });
    }

    public async getDataRecord(databaseId: string, recordId: string): Promise<Structures.DatabaseRecord> {
        return new Promise(async (res, rej) => {
            this._authRequest(`${databaseId}/record/${recordId}`).subscribe({
                next: async response => {
                    return res(new Structures.DatabaseRecord(response.data));
                },
                error: rej,
            });
        });
    }

    protected _authRequest(endpoint, params = {}, method: 'get' | 'post' = 'get') {
        return new Observable<ApiResponse>(sub => {
            this.getToken().then(token => {
                const headers = new HttpHeaders({
                    authorization: `bearer ${token}`
                });

                debug('Auth request headers', headers);

                if ( method === 'get' ) {
                    const data = {
                        params,
                        headers,
                    };

                    this.http.get(this.api._build_url(endpoint, this.databaseBase), data).subscribe({
                        next: (response: any) => {
                            return sub.next(new ApiResponse(response));
                        },
                        error: e => {
                            return sub.error(e);
                        },
                    });
                }
            });
        });
    }

    public async getToken(): Promise<string> {
        const now = (new Date()).getTime();

        // 1 hour
        if ( this.token && this.tokenTime && (now - this.tokenTime) < (60 * 60 * 1000) ) {
            return this.token;
        }

        this.token = await this.api.getToken();
        this.tokenTime = now;
        return this.token;
    }
}
