import {Injectable} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Observable} from 'rxjs';
import {GeoJSON} from 'ol/format';
import {map, switchMap} from 'rxjs/operators';
import {getCenter} from 'ol/extent';
import moment from 'moment';
import {AppConfig} from '../shared/app.config';
import {WeatherCurrentObservation} from '../models/weatherCurrentObservation';
import {IItem} from '../models/item';
import {HourlyWeatherData, WeatherData} from '../models/WeatherData';

@Injectable({
  providedIn: 'root',
})
export class WeatherService {
  private apiCurrentObservationUrl = 'currentobservation';
  private apiObservation = 'observation';
  private apiForecast = 'forecast';

  private format: GeoJSON;

  constructor(public httpClient: HttpClient, public appConfig: AppConfig) {
    this.format = new GeoJSON();
  }

  public GetCurrentObservation(centroid: number[]): Observable<WeatherCurrentObservation> {
    const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
    const apiKey = this.appConfig.getConfig('weatherApiKey');
    const observationUrl = `${endpoint}${this.apiCurrentObservationUrl}/?c=${centroid[0]},${centroid[1]}&key=${apiKey}`;

    return this.httpClient.get<WeatherCurrentObservation>(observationUrl);
  }

  public getWeatherRangeForItem(item: IItem, daysBefore = 10, daysAfter = 10): Observable<HourlyWeatherData[]> {
    const geometry = this.format.readGeometry(item.geometry);
    const centroid = getCenter(geometry.getExtent());

    const startDate = new Date();
    startDate.setDate(startDate.getDate() - daysBefore);
    startDate.setHours(0, 0, 0, 0);
    const startDateString = moment(startDate).local().format('YYYY-MM-DD[T]HH:mm:ss');

    const endDate = new Date();
    endDate.setDate(endDate.getDate() + daysAfter);
    endDate.setHours(23, 0, 0, 0);
    const endDateString = moment(endDate).local().format('YYYY-MM-DD[T]HH:mm:ss');

    return this.getWeatherRange(centroid, startDateString, endDateString);
  }

  public getDailyHistoricalObservations(centroid: number[], startDate: string, endDate: string): Observable<HourlyWeatherData[]> {
    const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
    const apiKey = this.appConfig.getConfig('weatherApiKey');

    // weather does not support UTC format, also remove Z
    const sd = encodeURIComponent(startDate);

    const edHistoricalDate = new Date();
    edHistoricalDate.setHours(edHistoricalDate.getHours(), 0, 0, 0);
    const edHistorical = moment(edHistoricalDate).local().format('YYYY-MM-DD[T]HH:mm:ss');

    const historical = `${endpoint}${this.apiObservation}/?c=${centroid[0]},${centroid[1]}&sd=${sd}&ed=${edHistorical}&t=cleanedobservation&interval=daily&key=${apiKey}`;

    return this.httpClient.get<any[]>(historical);
  }

  public getWeatherRange(centroid: number[], startDate: string, endDate: string): Observable<HourlyWeatherData[]> {
    const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
    const apiKey = this.appConfig.getConfig('weatherApiKey');

    // weather does not support UTC format, also remove Z
    const sd = encodeURIComponent(startDate);

    const edHistoricalDate = new Date();
    edHistoricalDate.setHours(edHistoricalDate.getHours(), 0, 0, 0);
    const edHistorical = moment(edHistoricalDate).local().format('YYYY-MM-DD[T]HH:mm:ss');

    const historical = `${endpoint}${this.apiObservation}/?c=${centroid[0]},${centroid[1]}&sd=${sd}&ed=${edHistorical}&t=observation&interval=hourly&key=${apiKey}`;
    const forecast = `${endpoint}${this.apiForecast}/?c=${centroid[0]},${centroid[1]}&interval=hourly&key=${apiKey}`;

    return this.httpClient.get<any[]>(historical).pipe(
      map(h => h.map(d => ({...d, rain: d.rainPastHour}))),
      switchMap(h => {
        return this.httpClient.get<any[]>(forecast)
          .pipe(
            map(f => [...h, ...f.filter(fd => fd.time <= endDate)] ));
      })
    );
  }

  public hourlyToDaily(hourlyWeatherData: HourlyWeatherData[]): WeatherData[] {
    const days = this.groupBy(hourlyWeatherData, hi => hi.time.split('T')[0]);
    return Object.entries(days)
      .reduce ( (result, entry) => {
        const wData = entry[1];

        const sortData = wData.sort(d => new Date(d.time).getHours() - 12);
        const closestToTwelveOClockData =  sortData[0];
        if (closestToTwelveOClockData.length === 0) {
          return result;
        }

        const bestCardinal = closestToTwelveOClockData.windDirectionCardinal;
        const bestWindDirection = closestToTwelveOClockData.windDirection;
        const bestIconCode = closestToTwelveOClockData.iconCode;
        return [
          ...result,
          {
            date: entry[0],
            iconCode: bestIconCode,
            minTemperature: Math.min(...wData.map(d => d.temperature)),
            maxTemperature: Math.max(...wData.map(d => d.temperature)),
            relHumidity: wData.reduce( (r, e) => r + e.relativeHumidity, 0) / wData.length,
            precipitation: wData.reduce( (r, e) => r + e.rain, 0),
            wSpeed: wData.reduce( (r, e) => r + e.windSpeed, 0) / wData.length,
            wDir: bestWindDirection,
            wCardinal: bestCardinal
          }
        ];
      }, []);
  }

  private groupBy(items: any[], selector: (item) => any): any[] {
    return items.reduce(
      (result, item) => {
        const group = selector(item);
        return ({
          ...result,
          [group]: [
            ...(result[group] || []),
            item,
          ],
        }); },
      {},
    );
  }
}
