use chrono::Utc;
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;
use vapora_shared::models::{Proposal, ProposalReview, ProposalStatus, RiskLevel};
use vapora_shared::{Result, VaporaError};

/// Service for managing proposals
#[derive(Clone)]
pub struct ProposalService {
    db: Surreal<Client>,
}

impl ProposalService {
    /// Create a new ProposalService instance
    pub fn new(db: Surreal<Client>) -> Self {
        Self { db }
    }

    /// Create a new proposal
    pub async fn create_proposal(&self, mut proposal: Proposal) -> Result<Proposal> {
        proposal.created_at = Utc::now();

        let json = serde_json::to_value(&proposal)?;
        let raw: Option<serde_json::Value> = self.db.create("proposals").content(json).await?;
        raw.and_then(|v| serde_json::from_value(v).ok())
            .ok_or_else(|| VaporaError::DatabaseError("Failed to create proposal".to_string()))
    }

    /// Get proposal by ID
    pub async fn get_proposal(&self, id: &str, tenant_id: &str) -> Result<Proposal> {
        let mut response = self
            .db
            .query("SELECT * FROM proposals WHERE id = $id AND tenant_id = $tenant_id")
            .bind(("id", id.to_string()))
            .bind(("tenant_id", tenant_id.to_string()))
            .await?;

        let raw: Vec<serde_json::Value> = response.take(0)?;
        raw.into_iter()
            .find_map(|v| serde_json::from_value(v).ok())
            .ok_or_else(|| VaporaError::NotFound(format!("Proposal not found: {}", id)))
    }

    /// List proposals for a project, optionally filtered by status
    pub async fn list_proposals(
        &self,
        project_id: &str,
        tenant_id: &str,
        status: Option<ProposalStatus>,
    ) -> Result<Vec<Proposal>> {
        let query = if status.is_some() {
            "SELECT * FROM proposals WHERE project_id = $project_id AND tenant_id = $tenant_id AND \
             status = $status ORDER BY created_at DESC"
                .to_string()
        } else {
            "SELECT * FROM proposals WHERE project_id = $project_id AND tenant_id = $tenant_id \
             ORDER BY created_at DESC"
                .to_string()
        };

        let mut response = self
            .db
            .query(&query)
            .bind(("project_id", project_id.to_string()))
            .bind(("tenant_id", tenant_id.to_string()));

        if let Some(s) = status {
            response = response.bind(("status", Self::status_to_string(&s)));
        }

        let mut response = response.await?;
        let raw: Vec<serde_json::Value> = response.take(0)?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// List proposals by task ID
    #[allow(dead_code)]
    pub async fn list_proposals_by_task(
        &self,
        task_id: &str,
        tenant_id: &str,
    ) -> Result<Vec<Proposal>> {
        let mut response = self
            .db
            .query(
                "SELECT * FROM proposals WHERE task_id = $task_id AND tenant_id = $tenant_id \
                 ORDER BY created_at DESC",
            )
            .bind(("task_id", task_id.to_string()))
            .bind(("tenant_id", tenant_id.to_string()))
            .await?;

        let raw: Vec<serde_json::Value> = response.take(0)?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Submit proposal for approval
    pub async fn submit_proposal(&self, id: &str, tenant_id: &str) -> Result<Proposal> {
        let mut proposal = self.get_proposal(id, tenant_id).await?;
        proposal.status = ProposalStatus::Proposed;
        proposal.submitted_at = Some(Utc::now());
        self.update_proposal(id, tenant_id, proposal).await
    }

    /// Approve proposal
    pub async fn approve_proposal(&self, id: &str, tenant_id: &str) -> Result<Proposal> {
        let mut proposal = self.get_proposal(id, tenant_id).await?;
        proposal.status = ProposalStatus::Approved;
        proposal.reviewed_at = Some(Utc::now());
        self.update_proposal(id, tenant_id, proposal).await
    }

    /// Reject proposal
    pub async fn reject_proposal(&self, id: &str, tenant_id: &str) -> Result<Proposal> {
        let mut proposal = self.get_proposal(id, tenant_id).await?;
        proposal.status = ProposalStatus::Rejected;
        proposal.reviewed_at = Some(Utc::now());
        self.update_proposal(id, tenant_id, proposal).await
    }

    /// Mark proposal as executed
    pub async fn mark_executed(&self, id: &str, tenant_id: &str) -> Result<Proposal> {
        let mut proposal = self.get_proposal(id, tenant_id).await?;
        proposal.status = ProposalStatus::Executed;
        proposal.executed_at = Some(Utc::now());
        self.update_proposal(id, tenant_id, proposal).await
    }

    /// Update proposal
    pub async fn update_proposal(
        &self,
        id: &str,
        tenant_id: &str,
        proposal: Proposal,
    ) -> Result<Proposal> {
        self.get_proposal(id, tenant_id).await?;

        let json = serde_json::to_value(&proposal)?;
        let raw: Option<serde_json::Value> =
            self.db.update(("proposals", id)).content(json).await?;
        raw.and_then(|v| serde_json::from_value(v).ok())
            .ok_or_else(|| VaporaError::DatabaseError("Failed to update proposal".to_string()))
    }

    /// Delete proposal
    pub async fn delete_proposal(&self, id: &str, tenant_id: &str) -> Result<()> {
        self.get_proposal(id, tenant_id).await?;
        let _: Option<serde_json::Value> = self.db.delete(("proposals", id)).await?;
        Ok(())
    }

    /// Add review to proposal
    pub async fn add_review(
        &self,
        proposal_id: &str,
        mut review: ProposalReview,
    ) -> Result<ProposalReview> {
        review.proposal_id = proposal_id.to_string();
        review.created_at = Utc::now();

        let json = serde_json::to_value(&review)?;
        let raw: Option<serde_json::Value> =
            self.db.create("proposal_reviews").content(json).await?;
        raw.and_then(|v| serde_json::from_value(v).ok())
            .ok_or_else(|| VaporaError::DatabaseError("Failed to create review".to_string()))
    }

    /// List reviews for proposal
    pub async fn list_reviews(&self, proposal_id: &str) -> Result<Vec<ProposalReview>> {
        let mut response = self
            .db
            .query(
                "SELECT * FROM proposal_reviews WHERE proposal_id = $proposal_id ORDER BY \
                 created_at DESC",
            )
            .bind(("proposal_id", proposal_id.to_string()))
            .await?;

        let raw: Vec<serde_json::Value> = response.take(0)?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Get proposals pending approval with risk level
    #[allow(dead_code)]
    pub async fn list_pending_by_risk(
        &self,
        tenant_id: &str,
        risk_level: RiskLevel,
    ) -> Result<Vec<Proposal>> {
        let risk_str = Self::risk_to_string(&risk_level);
        let status_str = Self::status_to_string(&ProposalStatus::Proposed);

        let mut response = self
            .db
            .query(
                "SELECT * FROM proposals WHERE tenant_id = $tenant_id AND status = $status AND \
                 risk_level = $risk_level ORDER BY created_at ASC",
            )
            .bind(("tenant_id", tenant_id.to_string()))
            .bind(("status", status_str))
            .bind(("risk_level", risk_str))
            .await?;

        let raw: Vec<serde_json::Value> = response.take(0)?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    fn status_to_string(status: &ProposalStatus) -> String {
        match status {
            ProposalStatus::Proposed => "proposed",
            ProposalStatus::Approved => "approved",
            ProposalStatus::Rejected => "rejected",
            ProposalStatus::Executed => "executed",
        }
        .to_string()
    }

    #[allow(dead_code)]
    fn risk_to_string(risk: &RiskLevel) -> String {
        match risk {
            RiskLevel::Low => "low",
            RiskLevel::Medium => "medium",
            RiskLevel::High => "high",
        }
        .to_string()
    }
}

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

    #[test]
    fn test_status_to_string() {
        assert_eq!(
            ProposalService::status_to_string(&ProposalStatus::Proposed),
            "proposed"
        );
        assert_eq!(
            ProposalService::status_to_string(&ProposalStatus::Approved),
            "approved"
        );
        assert_eq!(
            ProposalService::status_to_string(&ProposalStatus::Rejected),
            "rejected"
        );
        assert_eq!(
            ProposalService::status_to_string(&ProposalStatus::Executed),
            "executed"
        );
    }

    #[test]
    fn test_risk_to_string() {
        assert_eq!(ProposalService::risk_to_string(&RiskLevel::Low), "low");
        assert_eq!(
            ProposalService::risk_to_string(&RiskLevel::Medium),
            "medium"
        );
        assert_eq!(ProposalService::risk_to_string(&RiskLevel::High), "high");
    }
}
