use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize)]
pub struct GitLabUser {
    pub id: i64,
    pub username: String,
    pub name: String,
    pub email: Option<String>,
    pub avatar_url: Option<String>,
    pub web_url: Option<String>,
    pub created_at: Option<String>,
    pub state: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct GitLabProject {
    pub id: i64,
    pub path_with_namespace: String,
    pub default_branch: Option<String>,
    pub web_url: String,
    pub created_at: String,
    pub updated_at: String,
    pub name: Option<String>,
    pub description: Option<String>,
    pub visibility: Option<String>,
    pub archived: Option<bool>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct GitLabVersion {
    pub version: String,
    pub revision: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabAuthor {
    pub id: i64,
    pub username: String,
    pub name: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMilestone {
    pub id: i64,
    pub iid: i64,
    pub project_id: Option<i64>,
    pub title: String,
    pub description: Option<String>,
    pub state: Option<String>,
    pub due_date: Option<String>,
    pub web_url: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabIssue {
    pub id: i64,
    pub iid: i64,
    pub project_id: i64,
    pub title: String,
    pub description: Option<String>,
    pub state: String,
    pub created_at: String,
    pub updated_at: String,
    pub closed_at: Option<String>,
    pub author: GitLabAuthor,
    #[serde(default)]
    pub assignees: Vec<GitLabAuthor>,
    pub labels: Vec<String>,
    pub milestone: Option<GitLabMilestone>,
    pub due_date: Option<String>,
    pub web_url: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabDiscussion {
    pub id: String,
    pub individual_note: bool,
    pub notes: Vec<GitLabNote>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabNote {
    pub id: i64,
    #[serde(rename = "type")]
    pub note_type: Option<String>,
    pub body: String,
    pub author: GitLabAuthor,
    pub created_at: String,
    pub updated_at: String,
    pub system: bool,
    #[serde(default)]
    pub resolvable: bool,
    #[serde(default)]
    pub resolved: bool,
    pub resolved_by: Option<GitLabAuthor>,
    pub resolved_at: Option<String>,
    pub position: Option<GitLabNotePosition>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabNotePosition {
    pub old_path: Option<String>,
    pub new_path: Option<String>,
    pub old_line: Option<i32>,
    pub new_line: Option<i32>,
    pub position_type: Option<String>,
    pub line_range: Option<GitLabLineRange>,
    pub base_sha: Option<String>,
    pub start_sha: Option<String>,
    pub head_sha: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLineRange {
    pub start: GitLabLineRangePoint,
    pub end: GitLabLineRangePoint,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLineRangePoint {
    pub line_code: Option<String>,
    #[serde(rename = "type")]
    pub line_type: Option<String>,
    pub old_line: Option<i32>,
    pub new_line: Option<i32>,
}

impl GitLabLineRange {
    pub fn start_line(&self) -> Option<i32> {
        self.start.new_line.or(self.start.old_line)
    }

    pub fn end_line(&self) -> Option<i32> {
        self.end.new_line.or(self.end.old_line)
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMergeRequestRef {
    pub iid: i64,
    pub title: Option<String>,
    pub web_url: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLabelRef {
    pub id: i64,
    pub name: String,
    pub color: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMilestoneRef {
    pub id: i64,
    pub iid: i64,
    pub title: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabStateEvent {
    pub id: i64,
    pub user: Option<GitLabAuthor>,
    pub created_at: String,
    pub resource_type: String,
    pub resource_id: i64,
    pub state: String,
    pub source_commit: Option<String>,
    pub source_merge_request: Option<GitLabMergeRequestRef>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabLabelEvent {
    pub id: i64,
    pub user: Option<GitLabAuthor>,
    pub created_at: String,
    pub resource_type: String,
    pub resource_id: i64,
    pub label: Option<GitLabLabelRef>,
    pub action: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMilestoneEvent {
    pub id: i64,
    pub user: Option<GitLabAuthor>,
    pub created_at: String,
    pub resource_type: String,
    pub resource_id: i64,
    pub milestone: Option<GitLabMilestoneRef>,
    pub action: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabIssueRef {
    pub id: i64,
    pub iid: i64,
    pub project_id: i64,
    pub title: String,
    pub state: String,
    pub web_url: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabReferences {
    pub short: String,
    pub full: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabReviewer {
    pub id: i64,
    pub username: String,
    pub name: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMrDiff {
    pub old_path: String,
    pub new_path: String,
    #[serde(default)]
    pub new_file: bool,
    #[serde(default)]
    pub renamed_file: bool,
    #[serde(default)]
    pub deleted_file: bool,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GitLabMergeRequest {
    pub id: i64,
    pub iid: i64,
    pub project_id: i64,
    pub title: String,
    pub description: Option<String>,
    pub state: String,
    #[serde(default)]
    pub draft: bool,
    #[serde(default)]
    pub work_in_progress: bool,
    pub source_branch: String,
    pub target_branch: String,
    pub sha: Option<String>,
    pub references: Option<GitLabReferences>,
    pub detailed_merge_status: Option<String>,
    #[serde(alias = "merge_status")]
    pub merge_status_legacy: Option<String>,
    pub created_at: String,
    pub updated_at: String,
    pub merged_at: Option<String>,
    pub closed_at: Option<String>,
    pub author: GitLabAuthor,
    pub merge_user: Option<GitLabAuthor>,
    pub merged_by: Option<GitLabAuthor>,
    #[serde(default)]
    pub labels: Vec<String>,
    #[serde(default)]
    pub assignees: Vec<GitLabAuthor>,
    #[serde(default)]
    pub reviewers: Vec<GitLabReviewer>,
    pub web_url: String,
    pub merge_commit_sha: Option<String>,
    pub squash_commit_sha: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkItemStatus {
    pub name: String,
    pub category: Option<String>,
    pub color: Option<String>,
    #[serde(rename = "iconName")]
    pub icon_name: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_work_item_status_deserialize() {
        let json = r##"{"name":"In progress","category":"IN_PROGRESS","color":"#1f75cb","iconName":"status-in-progress"}"##;
        let status: WorkItemStatus = serde_json::from_str(json).unwrap();
        assert_eq!(status.name, "In progress");
        assert_eq!(status.category.as_deref(), Some("IN_PROGRESS"));
        assert_eq!(status.color.as_deref(), Some("#1f75cb"));
        assert_eq!(status.icon_name.as_deref(), Some("status-in-progress"));
    }

    #[test]
    fn test_work_item_status_optional_fields() {
        let json = r#"{"name":"To do"}"#;
        let status: WorkItemStatus = serde_json::from_str(json).unwrap();
        assert_eq!(status.name, "To do");
        assert!(status.category.is_none());
        assert!(status.color.is_none());
        assert!(status.icon_name.is_none());
    }

    #[test]
    fn test_work_item_status_unknown_category() {
        let json = r#"{"name":"Custom","category":"SOME_FUTURE_VALUE"}"#;
        let status: WorkItemStatus = serde_json::from_str(json).unwrap();
        assert_eq!(status.category.as_deref(), Some("SOME_FUTURE_VALUE"));
    }

    #[test]
    fn test_work_item_status_null_category() {
        let json = r#"{"name":"In progress","category":null}"#;
        let status: WorkItemStatus = serde_json::from_str(json).unwrap();
        assert!(status.category.is_none());
    }

    #[test]
    fn test_work_item_status_all_system_statuses() {
        let cases = [
            (
                r##"{"name":"To do","category":"TO_DO","color":"#737278"}"##,
                "TO_DO",
            ),
            (
                r##"{"name":"In progress","category":"IN_PROGRESS","color":"#1f75cb"}"##,
                "IN_PROGRESS",
            ),
            (
                r##"{"name":"Done","category":"DONE","color":"#108548"}"##,
                "DONE",
            ),
            (
                r##"{"name":"Won't do","category":"CANCELED","color":"#DD2B0E"}"##,
                "CANCELED",
            ),
            (
                r##"{"name":"Duplicate","category":"CANCELED","color":"#DD2B0E"}"##,
                "CANCELED",
            ),
        ];
        for (json, expected_cat) in cases {
            let status: WorkItemStatus = serde_json::from_str(json).unwrap();
            assert_eq!(
                status.category.as_deref(),
                Some(expected_cat),
                "Failed for: {}",
                status.name
            );
        }
    }
}
