use std::collections::BTreeMap;

use chrono::{DateTime, Utc};
use keepsake::{
    ActiveRelation, ExpiryPolicy, Keepsake, KeepsakeRecord, LifecycleState, RelationDefinition,
    RelationKey, SubjectRef,
};
use sqlx::FromRow;
use uuid::Uuid;

use super::{RepositoryError, RepositoryResult};

#[derive(Debug, FromRow)]
pub(super) struct RelationRow {
    id: Uuid,
    kind: String,
    key: String,
    enabled: bool,
    expiry_policy: serde_json::Value,
}

impl RelationRow {
    pub(super) fn try_into_relation(self) -> RepositoryResult<RelationDefinition> {
        let expiry = serde_json::from_value::<ExpiryPolicy>(self.expiry_policy)?;
        Ok(RelationDefinition::new(
            self.id,
            RelationKey::new(self.kind, self.key)?,
            self.enabled,
            expiry,
        )?)
    }
}

#[derive(Debug, FromRow)]
pub(super) struct AppliedKeepsakeRow {
    id: Uuid,
    subject_kind: String,
    subject_id: String,
    relation_id: Uuid,
    state: String,
    expiry_policy: serde_json::Value,
    applied_at: DateTime<Utc>,
    expires_at: Option<DateTime<Utc>>,
    fulfilled_at: Option<DateTime<Utc>>,
    revoked_at: Option<DateTime<Utc>>,
    metadata: serde_json::Value,
}

impl AppliedKeepsakeRow {
    pub(super) fn try_into_keepsake(self) -> RepositoryResult<Keepsake> {
        row_into_keepsake(KeepsakeRow {
            id: self.id,
            subject_kind: self.subject_kind,
            subject_id: self.subject_id,
            relation_id: self.relation_id,
            state: self.state,
            expiry_policy: self.expiry_policy,
            applied_at: self.applied_at,
            expires_at: self.expires_at,
            fulfilled_at: self.fulfilled_at,
            revoked_at: self.revoked_at,
            metadata: self.metadata,
        })
    }
}

#[derive(Debug, FromRow)]
pub(super) struct AppliedKeepsakeWriteRow {
    id: Uuid,
    subject_kind: String,
    subject_id: String,
    relation_id: Uuid,
    state: String,
    expiry_policy: serde_json::Value,
    applied_at: DateTime<Utc>,
    expires_at: Option<DateTime<Utc>>,
    fulfilled_at: Option<DateTime<Utc>>,
    revoked_at: Option<DateTime<Utc>>,
    metadata: serde_json::Value,
    pub(super) duplicate_prevented: bool,
}

impl AppliedKeepsakeWriteRow {
    pub(super) fn try_into_parts(self) -> RepositoryResult<(Keepsake, bool)> {
        let duplicate_prevented = self.duplicate_prevented;
        let keepsake = row_into_keepsake(KeepsakeRow {
            id: self.id,
            subject_kind: self.subject_kind,
            subject_id: self.subject_id,
            relation_id: self.relation_id,
            state: self.state,
            expiry_policy: self.expiry_policy,
            applied_at: self.applied_at,
            expires_at: self.expires_at,
            fulfilled_at: self.fulfilled_at,
            revoked_at: self.revoked_at,
            metadata: self.metadata,
        })?;
        Ok((keepsake, duplicate_prevented))
    }
}

#[derive(Debug, FromRow)]
pub(super) struct ActiveRelationRow {
    id: Uuid,
    subject_kind: String,
    subject_id: String,
    relation_id: Uuid,
    state: String,
    expiry_policy: serde_json::Value,
    applied_at: DateTime<Utc>,
    expires_at: Option<DateTime<Utc>>,
    fulfilled_at: Option<DateTime<Utc>>,
    revoked_at: Option<DateTime<Utc>>,
    metadata: serde_json::Value,
    relation_definition_id: Uuid,
    relation_kind: String,
    relation_key: String,
    relation_enabled: bool,
    relation_expiry_policy: serde_json::Value,
}

impl ActiveRelationRow {
    pub(super) fn try_into_active_relation(self) -> RepositoryResult<ActiveRelation> {
        let relation_expiry = serde_json::from_value::<ExpiryPolicy>(self.relation_expiry_policy)?;
        let keepsake = row_into_keepsake(KeepsakeRow {
            id: self.id,
            subject_kind: self.subject_kind,
            subject_id: self.subject_id,
            relation_id: self.relation_id,
            state: self.state,
            expiry_policy: self.expiry_policy,
            applied_at: self.applied_at,
            expires_at: self.expires_at,
            fulfilled_at: self.fulfilled_at,
            revoked_at: self.revoked_at,
            metadata: self.metadata,
        })?;
        let relation = RelationDefinition::new(
            self.relation_definition_id,
            RelationKey::new(self.relation_kind, self.relation_key)?,
            self.relation_enabled,
            relation_expiry,
        )?;
        Ok(ActiveRelation::new(keepsake, relation)?)
    }
}

struct KeepsakeRow {
    id: Uuid,
    subject_kind: String,
    subject_id: String,
    relation_id: Uuid,
    state: String,
    expiry_policy: serde_json::Value,
    applied_at: DateTime<Utc>,
    expires_at: Option<DateTime<Utc>>,
    fulfilled_at: Option<DateTime<Utc>>,
    revoked_at: Option<DateTime<Utc>>,
    metadata: serde_json::Value,
}

fn row_into_keepsake(row: KeepsakeRow) -> RepositoryResult<Keepsake> {
    let expiry = serde_json::from_value::<ExpiryPolicy>(row.expiry_policy)?;
    let metadata = serde_json::from_value::<BTreeMap<String, String>>(row.metadata)?;
    Ok(KeepsakeRecord {
        id: row.id,
        subject: SubjectRef {
            kind: row.subject_kind,
            id: row.subject_id,
        },
        relation_id: row.relation_id,
        state: parse_state(row.state)?,
        expiry,
        applied_at: row.applied_at,
        expires_at: row.expires_at,
        fulfilled_at: row.fulfilled_at,
        revoked_at: row.revoked_at,
        metadata,
    }
    .try_into()?)
}

pub(super) fn parse_state(value: String) -> RepositoryResult<LifecycleState> {
    match value.as_str() {
        "applied" => Ok(LifecycleState::Applied),
        "revoked" => Ok(LifecycleState::Revoked),
        "expired" => Ok(LifecycleState::Expired),
        _ => Err(RepositoryError::InvalidLifecycleState { state: value }),
    }
}
