import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnChanges, Input, SimpleChanges, ChangeDetectorRef } from '@angular/core';
import { Chart, registerables } from 'chart.js';
import { SensorService } from '../../../../cores/services/sensor.service';
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data'; 
import { CommonModule, formatDate } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { provideNativeDateAdapter} from '@angular/material/core';
import { MatDatepickerModule} from '@angular/material/datepicker';
import { MatFormFieldModule} from '@angular/material/form-field';
import { format } from 'date-fns';
import { ToastrService } from 'ngx-toastr';

Chart.register(...registerables); 

const parameterColors: { [key: string]: string } = {
  vicitemperature: '#8F5A62',
  vicihumidity: '#16423C',
  viciluminosity: '#DF9B55',
  soiltemperature: '#FF6347',  
  soilhumidity: '#0389b5',     
  soilconductivity: '#A52A2A',  
  soilph: '#228B22', 
  soilnitrogen: '#fece48',
  soilphosphorus: '#B80F0A',
  soilpotassium: '#4c1f74',
};

@Component({
  selector: 'app-graph',
  standalone: true,
  imports: [CommonModule, FormsModule, MatDatepickerModule, MatFormFieldModule],
  providers: [provideNativeDateAdapter()],
  templateUrl: './graph.component.html',
  styleUrls: ['./graph.component.scss']
})
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges{
  @ViewChild('myChartDHT', { static: false }) dhtChartElement!: ElementRef<HTMLCanvasElement>;
  @ViewChild('myChartNPK1', { static: false }) npk1ChartElement!: ElementRef<HTMLCanvasElement>;
  @ViewChild('myChartNPK2', { static: false }) npk2ChartElement!: ElementRef<HTMLCanvasElement>;
  @Input() interval: string = 'hourly';
  selectedInterval: string = 'HOURLY';

  startDate: Date | null = null;
  endDate: Date | null = null;

  isLoadingDHT: boolean = true;
  isLoadingNPK1: boolean = true;
  isLoadingNPK2: boolean = true;

  isNoDataDHT: boolean = false;
  isNoDataNPK1: boolean = false;
  isNoDataNPK2: boolean = false;

  allNoData: boolean = false;

  sensorParameters: { [key: string]: string[] } = {
    dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
    npk1: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'],
    npk2: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'],
  };

  parameterDisplayNames: { [key: string]: string } = {
    vicitemperature: 'Temperatur Udara (°C)',
    vicihumidity: 'Kelembaban Udara (%)',
    viciluminosity: 'Intensitas Cahaya (lux)',
    soiltemperature: 'Temperatur Tanah (°C)',
    soilhumidity: 'Kelembaban Tanah (%)',
    soilconductivity: 'Conductivity (μS/cm)',
    soilph: 'pH',
    soilnitrogen: 'Nitrogen (mg/l)',
    soilphosphorus: 'Phosphorus (mg/l)',
    soilpotassium: 'Kalium (mg/l)',
  };

  charts: { [key: string]: Chart | undefined } = {
    dht: undefined,
    npk1: undefined,
    npk2: undefined,
  };

  selectedNPK1: string = 'npk';
  selectedNPK2: string = 'npk';

  private resizeListener!: () => void; 
  private updateTimeout: any;

  constructor(private sensorService: SensorService, private toast: ToastrService) {}

  ngOnInit(): void {
    this.resizeListener = this.onResize.bind(this);
    window.addEventListener('resize', this.resizeListener);
  }
  

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['interval'] && changes['interval'].previousValue !== changes['interval'].currentValue) {
        this.selectedInterval = changes['interval'].currentValue; 
    }
  }

  ngAfterViewInit(): void {
    this.onResize();
  }

  dateChange(): void {
    if(!this.startDate || !this.endDate){
      this.toast.warning('Select both start and end dates');
      return;
    } else {
      clearTimeout(this.updateTimeout);
      this.updateTimeout = setTimeout(() => {
        this.updateCharts();
      }, 500);
    }
  }

  ngOnDestroy(): void {
    window.removeEventListener('resize', this.resizeListener); 
  }

  onResize(): void {
    Object.values(this.charts).forEach(chart => {
      if (chart) {
        chart.destroy();
      }
    });
    this.updateCharts();
  }

  formatDate(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

  getDate(): string {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0'); 
    const day = String(today.getDate()).padStart(2, '0');

    const hasil = `${year}-${month}-${day}`;
    return hasil;
  }

  getDateAgo():string{
    const today = new Date();
    today.setDate(today.getDate() - 7);

    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0'); 
    const day = String(today.getDate()).padStart(2, '0');
  
    return `${year}-${month}-${day}`;
  }

  fetchDHTData(timeRange: string, startDate?: Date, endDate?: Date): void {
    const hStart = startDate ? this.formatDate(startDate) :   this.getDate();
    const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
    const dStart= startDate ? this.formatDate(startDate) : this.getDateAgo();
    const dEnd = endDate ? this.formatDate(endDate) : this.getDate();

    if (timeRange === 'HOURLY') {
      this.sensorService.getSensorDataHourly('dht', 'npk', hStart, hEnd, timeRange).subscribe({
        next: (response) => {
          this.isLoadingDHT = false;
          if (response.statusCode === 200 && response.data.dht?.length > 0) {
            this.createChart(this.dhtChartElement.nativeElement, response, 'dht', 'npk');
            this.isNoDataDHT = false;
          } else {
            this.isNoDataDHT = true;
          }
        },
        error: () => {
          this.isLoadingDHT = false;
          this.isNoDataDHT = true;
        }
      });
    } else if (timeRange === 'DAILY') {
      this.sensorService.getSensorDataDaily('dht', 'npk', dStart, dEnd, timeRange).subscribe({
        next: (response) => {
          this.isLoadingDHT = false;
          if (response.statusCode === 200 && response.data.dht?.length > 0) {
            this.createChart(this.dhtChartElement.nativeElement, response, 'dht', 'npk');
            this.isNoDataDHT = false;
          } else {
            this.isNoDataDHT = true;
          }
        },
        error: () => {
          this.isLoadingDHT = false;
          this.isNoDataDHT = true;
        }
      });
    }
  }

  fetchNPK1Data(timeRange: string, startDate?: Date, endDate?: Date): void {
    const hStart = startDate ? this.formatDate(startDate) : this.getDate();
    const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
    const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
    const dEnd= endDate ? this.formatDate(endDate) : this.getDate();

    if(timeRange === 'HOURLY'){
      this.sensorService.getSensorDataHourly('npk1', this.selectedNPK1, hStart, hEnd, timeRange).subscribe({
        next: (response) => {
          this.isLoadingNPK1 = false;
          if (response.statusCode === 200 && response.data.npk1?.length > 0) {
            this.createChart(this.npk1ChartElement.nativeElement, response, 'npk1', this.selectedNPK1);
            this.isNoDataNPK1 = false;
          } else {
            this.isNoDataNPK1 = true;  
          }
        },
        error: () => {
          this.isLoadingNPK1 = false;
          this.isNoDataNPK1 = true;  
        }
      });
    }else if(timeRange === 'DAILY'){
      this.sensorService.getSensorDataDaily('npk1', this.selectedNPK1, dStart, dEnd, timeRange).subscribe({
        next: (response) => {
          this.isLoadingNPK1 = false;
          if (response.statusCode === 200 && response.data.npk1?.length > 0) {
            this.createChart(this.npk1ChartElement.nativeElement, response, 'npk1', this.selectedNPK1);
            this.isNoDataNPK1 = false;
          } else {
            this.isNoDataNPK1 = true;  
          }
        },
        error: () => {
          this.isLoadingNPK1 = false;
          this.isNoDataNPK1 = true;  
        }
      });
    }
  }

  fetchNPK2Data(savedTimeRange: string, startDate?: Date, endDate?: Date): void {
    const hStart = startDate ? this.formatDate(startDate) : this.getDate();
    const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
    const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
    const dEnd = endDate ? this.formatDate(endDate) : this.getDate();

    if(savedTimeRange === 'HOURLY'){
      this.sensorService.getSensorDataHourly('npk2', this.selectedNPK2, hStart, hEnd, savedTimeRange).subscribe({
        next: (response) => {
          this.isLoadingNPK2 = false;
          if (response.statusCode === 200 && response.data.npk2?.length > 0) {
            this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
            this.isNoDataNPK2 = false;
          } else {
            this.isNoDataNPK2 = true;  
          }
        },
        error: () => {
          this.isLoadingNPK2 = false;
          this.isNoDataNPK2 = true;  
        }
      });
    } else if(savedTimeRange === 'DAILY'){
      this.sensorService.getSensorDataDaily('npk2', this.selectedNPK2, dStart, dEnd, savedTimeRange).subscribe({
        next: (response) => {
          this.isLoadingNPK2 = false;
          if (response.statusCode === 200 && response.data.npk2?.length > 0) {
            this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
            this.isNoDataNPK2 = false;
          } else {
            this.isNoDataNPK2 = true;  
          }
        },
        error: () => {
          this.isLoadingNPK2 = false;
          this.isNoDataNPK2 = true;  
        }
      });
    }
  }

  updateCharts(): void {
    const interval = this.selectedInterval;
    
    Object.keys(this.charts).forEach(key => {
      if (this.charts[key]) {
        this.charts[key]?.destroy();
        this.charts[key].data.labels = [];
        this.charts[key].data.datasets = [];
        this.charts[key] = undefined;
      }
    });
    if (this.startDate && this.endDate) {
      this.fetchDHTData(interval, this.startDate, this.endDate);
      this.fetchNPK1Data(interval, this.startDate, this.endDate);
      this.fetchNPK2Data(interval, this.startDate, this.endDate);
    } else {
      this.fetchDHTData(interval);
      this.fetchNPK1Data(interval);
      this.fetchNPK2Data(interval);
    }
  }


  createChart(canvas: HTMLCanvasElement, response: ApiResponse, sensor: string, selectedOption: string): void {
    const ctx = canvas.getContext('2d');
    const parameters = this.sensorParameters[sensor];

    if (!ctx) {
        console.error('Failed to get canvas context for sensor:', sensor);
        return;
    }

    let datasets: any[] = [];

    if (sensor === 'dht') {
        datasets = ['vicitemperature', 'viciluminosity', 'vicihumidity'].map(parameter => {
            const { data, labels } = this.getDataFromResponse(response, sensor, parameter);

            if (data.length === 0) {
                console.warn(`No data found for parameter: ${parameter}`);
                return null;
            }

            const displayName = this.parameterDisplayNames[parameter] || parameter;
            const borderColor = parameterColors[parameter] || '#000000';
            const backgroundColor = `${borderColor}4D`;

            const pointRadius = data.length === 1 ? 5 : 0;
            const pointHoverRadius = data.length === 1 ? 7 : 0;

            return {
                label: displayName,
                data,
                borderColor,
                borderWidth: 1.5,
                fill: true,
                backgroundColor,
                tension: 0.5,
                pointRadius,
                pointHoverRadius,
            };
        }).filter(dataset => dataset !== null);
    } else {
        datasets = parameters
            .filter(parameter => {
                if (selectedOption === 'npk') {
                    return ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter);
                } else if (selectedOption === 'others') {
                    return !['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter);
                }
                return true;
            })
            .map(parameter => {
                const { data, labels } = this.getDataFromResponse(response, sensor, parameter);

                if (data.length === 0) {
                    console.warn(`No data found for parameter: ${parameter}`);
                    return null;
                }

                const displayName = this.parameterDisplayNames[parameter] || parameter;
                const borderColor = parameterColors[parameter] || '#000000';
                const backgroundColor = `${borderColor}4D`;

                const pointRadius = data.length === 1 ? 5 : 0;
                const pointHoverRadius = data.length === 1 ? 7 : 0;

                return {
                    label: displayName,
                    data,
                    borderColor,
                    borderWidth: 1.5,
                    fill: true,
                    backgroundColor,
                    tension: 0.5,
                    pointRadius,
                    pointHoverRadius,
                };
            })
            .filter(dataset => dataset !== null);
    }

    if (datasets.length === 0) {
        console.warn('No valid datasets to render for sensor:', sensor);
        return;
    }

    if (this.charts[sensor]) {
        this.charts[sensor]?.destroy();
    }


    this.charts[sensor] = new Chart(ctx, {
      type: 'line',
      data: {
          labels: this.getLabels(response, sensor),
          datasets,
      },
      options: {
          responsive: true,
          maintainAspectRatio: false,
          aspectRatio: 2,
          plugins: {
              tooltip: {
                  enabled: true,
                  mode: 'nearest',
                  intersect: false,
                  callbacks: {
                      label: (tooltipItem: any) => {
                          const paramLabel = tooltipItem.dataset.label;
                          const value = tooltipItem.formattedValue;
                          return `${paramLabel}: ${value}`;
                      }
                  }
              },
              legend: { display: true }
          },
          scales: {
              x: {
                  grid: { display: false },
                  beginAtZero: true,
                  ticks: {
                      callback: (value: string | number, index: number, values: any) => {
                          const labels = this.getLabels(response, sensor);
                          return labels[index] || value;
                      }
                  }
              },
              y: { grid: { display: false }, beginAtZero: true }
          }
      }
  });
  
  }

  getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
    const sensorData = response.data[sensor as keyof typeof response.data];

    const data: number[] = [];
    const labels: string[] = [];

    if (sensorData) {
      sensorData.forEach(item => {
        data.push(item[parameter as keyof ParameterSensor] ?? 0);
        if(this.interval === 'HOURLY') {
          labels.push(item.date);
          labels.push(item.hour);
        } 
        else if (this.interval === 'DAILY') {
          labels.push(item.day);
        }
      });
    } else {
      console.error('No data found for sensor:', sensor);
    }

    return { data, labels };
  }

  getLabels(response: ApiResponse, sensor: string): string[] {
    const sensorData = response.data[sensor as keyof typeof response.data];
  
    return sensorData.map(item => {
      const formatDate = format(new Date(item.date), 'MMM dd');

      if (this.interval === 'HOURLY' || this.selectedInterval === 'HOURLY') {
        return `${formatDate}, ${item.hour}.00`;
      } else if (this.interval === 'DAILY') {
        const day = item.date;  
        const convertDateToDay = this.convertDateToDay(day);
        return `${convertDateToDay}, ${formatDate}`;
      } else {
        return '';
      }
    });
  }
  
  convertDateToDay(dateString: string): string {
    const date = new Date(dateString);
    const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thrus', 'Fri', 'Sat'];
    return days[date.getDay()];
  }
  
}
