// SPDX-FileCopyrightText: 2025 Zexin Yuan <aim@yzx9.xyz>
//
// SPDX-License-Identifier: Apache-2.0

use std::{fmt::Display, num::NonZeroU32, str::FromStr};

use chrono::{DateTime, Local};
use icalendar::{Component, EventLike};

use crate::LooseDateTime;

/// Trait representing a calendar event.
pub trait Event {
    /// The short identifier for the event.
    /// It will be `None` if the event does not have a short ID.
    /// It is used for display purposes and may not be unique.
    fn short_id(&self) -> Option<NonZeroU32> {
        None
    }

    /// The unique identifier for the event.
    fn uid(&self) -> &str;

    /// The description of the event, if available.
    fn description(&self) -> Option<&str>;

    /// The location of the event, if available.
    fn start(&self) -> Option<LooseDateTime>;

    /// The start date and time of the event, if available.
    fn end(&self) -> Option<LooseDateTime>;

    /// The status of the event, if available.
    fn status(&self) -> Option<EventStatus>;

    /// The summary of the event.
    fn summary(&self) -> &str;
}

impl Event for icalendar::Event {
    fn uid(&self) -> &str {
        self.get_uid().unwrap_or_default()
    }

    fn description(&self) -> Option<&str> {
        self.get_description()
    }

    fn start(&self) -> Option<LooseDateTime> {
        self.get_start().map(Into::into)
    }

    fn end(&self) -> Option<LooseDateTime> {
        self.get_end().map(Into::into)
    }

    fn status(&self) -> Option<EventStatus> {
        self.get_status().map(EventStatus::from)
    }

    fn summary(&self) -> &str {
        self.get_summary().unwrap_or_default()
    }
}

/// Darft for an event, used for creating new events.
#[derive(Debug)]
pub struct EventDraft {
    /// The description of the event, if available.
    pub description: Option<String>,

    /// The start date and time of the event, if available.
    pub start: Option<LooseDateTime>,

    /// The end date and time of the event, if available.
    pub end: Option<LooseDateTime>,

    /// The status of the event.
    pub status: EventStatus,

    /// The summary of the event.
    pub summary: String,
}

impl EventDraft {
    /// Creates a new empty patch.
    pub(crate) fn default() -> Self {
        Self {
            description: None,
            start: None,
            end: None,
            status: EventStatus::default(),
            summary: String::new(),
        }
    }

    /// Converts the draft into a icalendar Event component.
    pub(crate) fn into_ics(self, uid: &str) -> icalendar::Event {
        let mut event = icalendar::Event::with_uid(uid);

        if let Some(description) = self.description {
            Component::description(&mut event, &description);
        }

        if let Some(start) = self.start {
            EventLike::starts(&mut event, start);
        }

        if let Some(end) = self.end {
            EventLike::ends(&mut event, end);
        }

        icalendar::Event::status(&mut event, self.status.into());

        Component::summary(&mut event, &self.summary);

        event
    }
}

/// Patch for an event, allowing partial updates.
#[derive(Debug, Default, Clone)]
pub struct EventPatch {
    /// The description of the event, if available.
    pub description: Option<Option<String>>,

    /// The start date and time of the event, if available.
    pub start: Option<Option<LooseDateTime>>,

    /// The end date and time of the event, if available.
    pub end: Option<Option<LooseDateTime>>,

    /// The status of the event, if available.
    pub status: Option<EventStatus>,

    /// The summary of the event, if available.
    pub summary: Option<String>,
}

impl EventPatch {
    /// Is this patch empty, meaning no fields are set
    pub fn is_empty(&self) -> bool {
        self.description.is_none()
            && self.start.is_none()
            && self.end.is_none()
            && self.status.is_none()
            && self.summary.is_none()
    }

    /// Applies the patch to a mutable event, modifying it in place.
    pub(crate) fn apply_to<'a>(&self, e: &'a mut icalendar::Event) -> &'a mut icalendar::Event {
        if let Some(description) = &self.description {
            match description {
                Some(desc) => e.description(desc),
                None => e.remove_description(),
            };
        }

        if let Some(start) = &self.start {
            match start {
                Some(s) => e.starts(*s),
                None => e.remove_starts(),
            };
        }

        if let Some(end) = &self.end {
            match end {
                Some(ed) => e.ends(*ed),
                None => e.remove_ends(),
            };
        }

        if let Some(status) = self.status {
            e.status(status.into());
        }

        if let Some(summary) = &self.summary {
            e.summary(summary);
        }

        e
    }
}

/// The status of an event, which can be tentative, confirmed, or cancelled.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum EventStatus {
    /// The event is tentative.
    Tentative,

    /// The event is confirmed.
    #[default]
    Confirmed,

    /// The event is cancelled.
    Cancelled,
}

const STATUS_TENTATIVE: &str = "TENTATIVE";
const STATUS_CONFIRMED: &str = "CONFIRMED";
const STATUS_CANCELLED: &str = "CANCELLED";

impl AsRef<str> for EventStatus {
    fn as_ref(&self) -> &str {
        match self {
            EventStatus::Tentative => STATUS_TENTATIVE,
            EventStatus::Confirmed => STATUS_CONFIRMED,
            EventStatus::Cancelled => STATUS_CANCELLED,
        }
    }
}

impl Display for EventStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

impl FromStr for EventStatus {
    type Err = ();

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            STATUS_TENTATIVE => Ok(EventStatus::Tentative),
            STATUS_CONFIRMED => Ok(EventStatus::Confirmed),
            STATUS_CANCELLED => Ok(EventStatus::Cancelled),
            _ => Err(()),
        }
    }
}

impl From<EventStatus> for icalendar::EventStatus {
    fn from(status: EventStatus) -> Self {
        match status {
            EventStatus::Tentative => icalendar::EventStatus::Tentative,
            EventStatus::Confirmed => icalendar::EventStatus::Confirmed,
            EventStatus::Cancelled => icalendar::EventStatus::Cancelled,
        }
    }
}

impl From<icalendar::EventStatus> for EventStatus {
    fn from(status: icalendar::EventStatus) -> Self {
        match status {
            icalendar::EventStatus::Tentative => EventStatus::Tentative,
            icalendar::EventStatus::Confirmed => EventStatus::Confirmed,
            icalendar::EventStatus::Cancelled => EventStatus::Cancelled,
        }
    }
}

/// Conditions for filtering events in a calendar.
#[derive(Debug, Clone, Copy)]
pub struct EventConditions {
    /// Whether to include only startable events.
    pub startable: bool,
}

#[derive(Debug)]
pub(crate) struct ParsedEventConditions {
    /// The date and time after which the event must end to be considered startable.
    pub end_after: Option<DateTime<Local>>,
}

impl ParsedEventConditions {
    pub fn parse(now: &DateTime<Local>, conds: &EventConditions) -> Self {
        Self {
            end_after: conds.startable.then_some(*now),
        }
    }
}
