#include "date_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_DATE

#include "esphome/core/log.h"

namespace esphome::datetime {

static const char *const TAG = "datetime.date_entity";

void DateEntity::publish_state() {
  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
    this->set_has_state(false);
    return;
  }
  if (this->year_ < 1970 || this->year_ > 3000) {
    this->set_has_state(false);
    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
    return;
  }
  if (this->month_ < 1 || this->month_ > 12) {
    this->set_has_state(false);
    ESP_LOGE(TAG, "Month must be between 1 and 12");
    return;
  }
  if (this->day_ > days_in_month(this->month_, this->year_)) {
    this->set_has_state(false);
    ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
    return;
  }
  this->set_has_state(true);
  ESP_LOGV(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
  this->state_callback_.call();
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
  ControllerRegistry::notify_date_update(this);
#endif
}

DateCall DateEntity::make_call() { return DateCall(this); }

void DateCall::validate_() {
  if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
    this->year_.reset();
    this->month_.reset();
    this->day_.reset();
  }
  if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
    ESP_LOGE(TAG, "Month must be between 1 and 12");
    this->month_.reset();
    this->day_.reset();
  }
  if (this->day_.has_value()) {
    uint16_t year = 0;
    uint8_t month = 0;
    if (this->month_.has_value()) {
      month = *this->month_;
    } else {
      if (this->parent_->month != 0) {
        month = this->parent_->month;
      } else {
        ESP_LOGE(TAG, "Month must be set to validate day");
        this->day_.reset();
      }
    }
    if (this->year_.has_value()) {
      year = *this->year_;
    } else {
      if (this->parent_->year != 0) {
        year = this->parent_->year;
      } else {
        ESP_LOGE(TAG, "Year must be set to validate day");
        this->day_.reset();
      }
    }
    if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
      ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
      this->day_.reset();
    }
  }
}

void DateCall::perform() {
  this->validate_();
  ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());

  if (this->year_.has_value()) {
    ESP_LOGV(TAG, " Year: %d", *this->year_);
  }
  if (this->month_.has_value()) {
    ESP_LOGV(TAG, " Month: %d", *this->month_);
  }
  if (this->day_.has_value()) {
    ESP_LOGV(TAG, " Day: %d", *this->day_);
  }
  this->parent_->control(*this);
}

DateCall &DateCall::set_date(uint16_t year, uint8_t month, uint8_t day) {
  this->year_ = year;
  this->month_ = month;
  this->day_ = day;
  return *this;
};

DateCall &DateCall::set_date(ESPTime time) { return this->set_date(time.year, time.month, time.day_of_month); };

DateCall &DateCall::set_date(const char *date, size_t len) {
  ESPTime val{};
  if (!ESPTime::strptime(date, len, val)) {
    ESP_LOGE(TAG, "Could not convert the date string to an ESPTime object");
    return *this;
  }
  return this->set_date(val);
}

DateCall DateEntityRestoreState::to_call(DateEntity *date) {
  DateCall call = date->make_call();
  call.set_date(this->year, this->month, this->day);
  return call;
}

void DateEntityRestoreState::apply(DateEntity *date) {
  date->year_ = this->year;
  date->month_ = this->month;
  date->day_ = this->day;
  date->publish_state();
}

}  // namespace esphome::datetime

#endif  // USE_DATETIME_DATE
