use uuid::Uuid;
use thiserror::Error;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use sqlx::FromRow;

// ============= ОШИБКИ =============
#[derive(Error, Debug)]
pub enum MarketingError {
    #[error("Потенциал вовлеченности {0} слишком мал (мин: 0.5)")]
    LowEngagement(f64),
    
    #[error("Контент не в том состоянии: {0}")]
    InvalidState(String),
    
    #[error("Кампания с ID {0} не найдена")]
    CampaignNotFound(Uuid),
    
    #[error("База данных: {0}")]
    Database(String),
}

// ============= СОСТОЯНИЯ =============
#[derive(Debug, Clone, sqlx::Type, PartialEq)]
#[sqlx(type_name = "TEXT", rename_all = "lowercase")]
pub enum ContentState {
    Draft,
    Validated,
    Published,
}

impl ContentState {
    pub fn as_str(&self) -> &'static str {
        match self {
            ContentState::Draft => "draft",
            ContentState::Validated => "validated",
            ContentState::Published => "published",
        }
    }
}

// ============= МОДЕЛИ =============
#[derive(Debug, Clone, FromRow)]
pub struct Campaign {
    pub id: Uuid,
    pub title: String,
    pub budget: f64,
    pub fluxion_target: f64,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone, FromRow)]
pub struct Content {
    pub id: Uuid,
    pub campaign_id: Uuid,
    pub title: String,
    pub state: ContentState,
    pub engagement_potential: Option<f64>,
    pub url: Option<String>,
    pub leads_generated: Option<i32>,
    pub roi: Option<f64>,
    pub created_at: DateTime<Utc>,
    pub validated_at: Option<DateTime<Utc>>,
    pub published_at: Option<DateTime<Utc>>,
}

// ============= БИЗНЕС-ЛОГИКА =============
impl Content {
    pub fn new(campaign_id: Uuid, title: &str) -> Self {
        Self {
            id: Uuid::new_v4(),
            campaign_id,
            title: title.to_string(),
            state: ContentState::Draft,
            engagement_potential: None,
            url: None,
            leads_generated: None,
            roi: None,
            created_at: Utc::now(),
            validated_at: None,
            published_at: None,
        }
    }
    
    pub fn validate(mut self, engagement: f64) -> Result<Self, MarketingError> {
        if engagement < 0.5 {
            return Err(MarketingError::LowEngagement(engagement));
        }
        
        self.state = ContentState::Validated;
        self.engagement_potential = Some(engagement);
        self.validated_at = Some(Utc::now());
        
        Ok(self)
    }
    
    pub fn publish(mut self, url: &str) -> Result<Self, MarketingError> {
        match self.state {
            ContentState::Validated => {
                let engagement = self.engagement_potential
                    .ok_or_else(|| MarketingError::InvalidState("Нет потенциала".into()))?;
                
                let leads = (engagement * 10.0) as i32;
                let roi = (leads as f64 / 100.0) * 100.0;
                
                self.state = ContentState::Published;
                self.url = Some(url.to_string());
                self.leads_generated = Some(leads);
                self.roi = Some(roi);
                self.published_at = Some(Utc::now());
                
                Ok(self)
            }
            _ => Err(MarketingError::InvalidState(
                format!("Нельзя опубликовать из состояния {:?}", self.state)
            )),
        }
    }
    
    pub fn get_roi(&self) -> f64 {
        self.roi.unwrap_or(0.0)
    }
}

// ============= БИЛДЕР =============
#[derive(Default)]
pub struct CampaignBuilder {
    title: Option<String>,
    budget: Option<f64>,
    fluxion_target: Option<f64>,
}

impl CampaignBuilder {
    pub fn new() -> Self {
        Self::default()
    }
    
    pub fn title(mut self, title: &str) -> Self {
        self.title = Some(title.to_string());
        self
    }
    
    pub fn budget(mut self, budget: f64) -> Self {
        self.budget = Some(budget);
        self
    }
    
    pub fn fluxion_target(mut self, target: f64) -> Self {
        self.fluxion_target = Some(target);
        self
    }
    
    pub fn build(self) -> Result<Campaign> {
        Ok(Campaign {
            id: Uuid::new_v4(),
            title: self.title.context("Название кампании обязательно")?,
            budget: self.budget.context("Бюджет обязателен")?,
            fluxion_target: self.fluxion_target.context("Fluxion target обязателен")?,
            created_at: Utc::now(),
            updated_at: Utc::now(),
        })
    }
}

// ============= РЕПОЗИТОРИЙ =============
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};

#[derive(Clone)]
pub struct MarketingRepository {
    pool: SqlitePool,
}

impl MarketingRepository {
    pub async fn new(database_url: &str) -> Result<Self> {
        let pool = SqlitePoolOptions::new()
            .max_connections(5)
            .connect(database_url)
            .await
            .map_err(|e| MarketingError::Database(e.to_string()))?;
            
        // Создаем таблицы, если их нет
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS campaigns (
                id TEXT PRIMARY KEY,
                title TEXT NOT NULL,
                budget REAL NOT NULL,
                fluxion_target REAL NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            "#
        )
        .execute(&pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS contents (
                id TEXT PRIMARY KEY,
                campaign_id TEXT NOT NULL,
                title TEXT NOT NULL,
                state TEXT NOT NULL,
                engagement_potential REAL,
                url TEXT,
                leads_generated INTEGER,
                roi REAL,
                created_at TEXT NOT NULL,
                validated_at TEXT,
                published_at TEXT,
                FOREIGN KEY (campaign_id) REFERENCES campaigns(id)
            )
            "#
        )
        .execute(&pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        sqlx::query(
            r#"
            CREATE INDEX IF NOT EXISTS idx_contents_campaign_id 
            ON contents(campaign_id)
            "#
        )
        .execute(&pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        Ok(Self { pool })
    }
    
    pub async fn create_campaign(&self, campaign: &Campaign) -> Result<()> {
        sqlx::query(
            r#"
            INSERT INTO campaigns (id, title, budget, fluxion_target, created_at, updated_at)
            VALUES ($1, $2, $3, $4, $5, $6)
            "#
        )
        .bind(campaign.id.to_string())
        .bind(&campaign.title)
        .bind(campaign.budget)
        .bind(campaign.fluxion_target)
        .bind(campaign.created_at.to_rfc3339())
        .bind(campaign.updated_at.to_rfc3339())
        .execute(&self.pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        Ok(())
    }
    
    pub async fn get_campaign(&self, id: Uuid) -> Result<Option<Campaign>> {
        let campaign = sqlx::query_as::<_, Campaign>(
            r#"
            SELECT id, title, budget, fluxion_target, created_at, updated_at
            FROM campaigns
            WHERE id = $1
            "#
        )
        .bind(id.to_string())
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        Ok(campaign)
    }
    
    pub async fn save_content(&self, content: &Content) -> Result<()> {
        sqlx::query(
            r#"
            INSERT INTO contents (
                id, campaign_id, title, state, engagement_potential,
                url, leads_generated, roi, created_at, validated_at, published_at
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
            "#
        )
        .bind(content.id.to_string())
        .bind(content.campaign_id.to_string())
        .bind(&content.title)
        .bind(content.state.as_str())
        .bind(content.engagement_potential)
        .bind(&content.url)
        .bind(content.leads_generated)
        .bind(content.roi)
        .bind(content.created_at.to_rfc3339())
        .bind(content.validated_at.map(|dt| dt.to_rfc3339()))
        .bind(content.published_at.map(|dt| dt.to_rfc3339()))
        .execute(&self.pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        Ok(())
    }
    
    pub async fn get_campaign_contents(&self, campaign_id: Uuid) -> Result<Vec<Content>> {
        let contents = sqlx::query_as::<_, Content>(
            r#"
            SELECT id, campaign_id, title, state, engagement_potential,
                   url, leads_generated, roi, created_at, validated_at, published_at
            FROM contents
            WHERE campaign_id = $1
            ORDER BY created_at DESC
            "#
        )
        .bind(campaign_id.to_string())
        .fetch_all(&self.pool)
        .await
        .map_err(|e| MarketingError::Database(e.to_string()))?;
        
        Ok(contents)
    }
}
