use chrono::Utc;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use serde_json::{Value, json};
use std::io::Cursor;
use teehistorian::ThWriter;
use uuid::Uuid;

use crate::errors::TeehistorianParseError;

/// Teehistorian file writer
///
/// This struct provides functionality to create valid teehistorian files
/// in a Pythonic way by accepting file-like objects and chunk objects.
#[pyclass(name = "TeehistorianWriter", module = "teehistorian_py")]
pub struct PyTeehistorianWriter {
    buffer: Vec<u8>,
    header_written: bool,
    header_data: Value,
}

#[pymethods]
impl PyTeehistorianWriter {
    /// Create a new teehistorian writer
    ///
    /// # Arguments
    /// * `file` - Optional file-like object to write to
    ///
    /// # Example
    /// ```python
    /// # Write to memory
    /// writer = th.TeehistorianWriter()
    ///
    /// # Write to file
    /// with open("output.teehistorian", "wb") as f:
    ///     writer = th.TeehistorianWriter(f)
    /// ```
    #[new]
    #[pyo3(signature = (_file = None))]
    fn new(_file: Option<&Bound<'_, PyAny>>) -> Self {
        let default_header = json!({
            "version": "2",
            "version_minor": "9",
            "comment": "teehistorian-py",
            "start_time": Utc::now().to_rfc3339(),
            "game_uuid": Uuid::nil().to_string(),
            "prev_game_uuid": Uuid::nil().to_string(),
            "server_version": "teehistorian-py 2.0.0",
            "server_name": "teehistorian-py Server"
        });

        Self {
            buffer: Vec::new(),
            header_written: false,
            header_data: default_header,
        }
    }

    /// Write a chunk to the teehistorian
    ///
    /// # Arguments
    /// * `chunk` - A chunk object (Join, Drop, PlayerNew, etc.)
    ///
    /// # Example
    /// ```python
    /// writer = th.TeehistorianWriter()
    /// writer.write(th.Join(0))
    /// writer.write(th.PlayerName(0, "Player"))
    /// writer.write(th.Drop(0, "quit"))
    /// writer.write(th.Eos())
    /// ```
    fn write(&mut self, _py: Python<'_>, chunk: &Bound<'_, PyAny>) -> PyResult<()> {
        // Ensure header is written first
        if !self.header_written {
            self.write_header()?;
        }

        // Get the serialized data from the chunk
        let py_bytes = chunk.call_method0("write_to_buffer")?;
        let chunk_data: Vec<u8> = py_bytes.extract()?;

        // Append to our buffer
        self.buffer.extend_from_slice(&chunk_data);

        Ok(())
    }

    /// Write multiple chunks at once
    ///
    /// # Arguments
    /// * `chunks` - List of chunk objects
    ///
    /// # Example
    /// ```python
    /// writer = th.TeehistorianWriter()
    /// writer.write_all([
    ///     th.Join(0),
    ///     th.PlayerName(0, "Player"),
    ///     th.Eos()
    /// ])
    /// ```
    fn write_all(&mut self, py: Python<'_>, chunks: Vec<Py<PyAny>>) -> PyResult<()> {
        for chunk in chunks {
            self.write(py, chunk.bind(py))?;
        }
        Ok(())
    }

    /// Set header field value
    ///
    /// # Arguments
    /// * `key` - Header field name
    /// * `value` - Header field value
    ///
    /// # Example
    /// ```python
    /// writer.set_header("server_name", "My Custom Server")
    /// writer.set_header("comment", "Generated by my script")
    /// ```
    fn set_header(&mut self, key: String, value: String) -> PyResult<()> {
        if self.header_written {
            return Err(TeehistorianParseError::Validation(
                "Cannot modify header after writing has started".to_string(),
            )
            .into());
        }

        self.header_data[key] = Value::String(value);
        Ok(())
    }

    /// Get header field value
    ///
    /// # Arguments
    /// * `key` - Header field name
    ///
    /// # Returns
    /// Header field value or None if not set
    fn get_header(&self, key: String) -> Option<String> {
        self.header_data
            .get(&key)
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
    }

    /// Update multiple header fields from a dictionary
    ///
    /// # Arguments
    /// * `headers` - Dictionary of field names and values
    ///
    /// # Example
    /// ```python
    /// writer.update_headers({
    ///     "server_name": "My Server",
    ///     "comment": "Custom teehistorian file"
    /// })
    /// ```
    fn update_headers(
        &mut self,
        _py: Python<'_>,
        headers: &Bound<'_, pyo3::types::PyDict>,
    ) -> PyResult<()> {
        if self.header_written {
            return Err(TeehistorianParseError::Validation(
                "Cannot modify header after writing has started".to_string(),
            )
            .into());
        }

        for (key, value) in headers.iter() {
            let key_str = key.extract::<String>()?;
            let value_str = value.extract::<String>()?;
            self.header_data[key_str] = Value::String(value_str);
        }

        Ok(())
    }

    /// Get all current data as bytes
    ///
    /// # Returns
    /// All written data as bytes
    ///
    /// # Example
    /// ```python
    /// writer = th.TeehistorianWriter()
    /// writer.write(th.Join(0))
    /// writer.write(th.Eos())
    /// data = writer.getvalue()  # Like BytesIO.getvalue()
    /// ```
    fn getvalue(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        // Ensure header is written
        if !self.header_written {
            self.write_header()?;
        }

        Ok(PyBytes::new(py, &self.buffer).into())
    }

    /// Save to file path
    ///
    /// # Arguments
    /// * `path` - File path to save to
    ///
    /// # Example
    /// ```python
    /// writer = th.TeehistorianWriter()
    /// writer.write(th.Join(0))
    /// writer.write(th.Eos())
    /// writer.save("output.teehistorian")
    /// ```
    fn save(&mut self, path: String) -> PyResult<()> {
        // Ensure header is written
        if !self.header_written {
            self.write_header()?;
        }

        std::fs::write(&path, &self.buffer).map_err(|e| {
            TeehistorianParseError::File(format!("Failed to save to {}: {}", path, e))
        })?;
        Ok(())
    }

    /// Write all data to a file-like object
    ///
    /// # Arguments
    /// * `file` - File-like object with write() method
    ///
    /// # Example
    /// ```python
    /// writer = th.TeehistorianWriter()
    /// writer.write(th.Join(0))
    /// writer.write(th.Eos())
    ///
    /// with open("output.teehistorian", "wb") as f:
    ///     writer.writeto(f)
    /// ```
    fn writeto(&mut self, py: Python<'_>, file: &Bound<'_, PyAny>) -> PyResult<()> {
        // Ensure header is written
        if !self.header_written {
            self.write_header()?;
        }

        // Write to file-like object
        let data = PyBytes::new(py, &self.buffer);
        file.call_method1("write", (data,))?;

        Ok(())
    }

    /// Get current buffer size
    fn size(&self) -> usize {
        self.buffer.len()
    }

    /// Reset the writer to initial state
    fn reset(&mut self) {
        self.buffer.clear();
        self.header_written = false;

        // Reset to default header
        self.header_data = json!({
            "version": "2",
            "version_minor": "9",
            "comment": "teehistorian-py",
            "start_time": Utc::now().to_rfc3339(),
            "game_uuid": Uuid::nil().to_string(),
            "prev_game_uuid": Uuid::nil().to_string(),
            "server_version": "teehistorian-py 2.0.0",
            "server_name": "teehistorian-py Server"
        });
    }

    /// Check if any data has been written
    fn is_empty(&self) -> bool {
        self.buffer.is_empty() && !self.header_written
    }

    /// Context manager support
    fn __enter__(slf: Py<Self>) -> Py<Self> {
        slf
    }

    /// Context manager exit - ensures EOS is written if not already present
    fn __exit__(
        &mut self,
        _py: Python<'_>,
        _exc_type: Option<&Bound<'_, pyo3::types::PyAny>>,
        _exc_value: Option<&Bound<'_, pyo3::types::PyAny>>,
        _traceback: Option<&Bound<'_, pyo3::types::PyAny>>,
    ) -> PyResult<bool> {
        // For safety, we don't auto-write chunks in __exit__
        Ok(false)
    }

    /// String representation
    fn __repr__(&self) -> String {
        let status = if self.header_written {
            "started"
        } else {
            "empty"
        };
        format!(
            "TeehistorianWriter(size={}, status={})",
            self.buffer.len(),
            status
        )
    }
}

impl PyTeehistorianWriter {
    /// Write the header to the buffer
    fn write_header(&mut self) -> PyResult<()> {
        if self.header_written {
            return Ok(());
        }

        // Create in-memory writer
        let mut cursor = Cursor::new(Vec::new());

        // Create ThWriter and write header
        let header_str = serde_json::to_string(&self.header_data).map_err(|e| {
            TeehistorianParseError::Write(format!("Failed to serialize header: {}", e))
        })?;

        let _writer = ThWriter::new(&mut cursor, &header_str).map_err(|e| {
            TeehistorianParseError::Write(format!("Failed to create writer: {}", e))
        })?;

        // Get the written data
        let written_data = cursor.into_inner();
        self.buffer.extend_from_slice(&written_data);
        self.header_written = true;

        Ok(())
    }
}

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

    #[test]
    fn test_writer_creation() {
        let writer = PyTeehistorianWriter::new(None);
        assert!(!writer.header_written);
        assert!(writer.buffer.is_empty());
        assert!(writer.is_empty());
    }

    #[test]
    fn test_header_operations() {
        let mut writer = PyTeehistorianWriter::new(None);

        // Test setting and getting header fields
        writer
            .set_header("test_key".to_string(), "test_value".to_string())
            .unwrap();
        assert_eq!(
            writer.get_header("test_key".to_string()),
            Some("test_value".to_string())
        );

        // Test getting non-existent field
        assert_eq!(writer.get_header("non_existent".to_string()), None);
    }

    #[test]
    fn test_reset() {
        let mut writer = PyTeehistorianWriter::new(None);
        writer.write_header().unwrap();
        assert!(!writer.is_empty());

        writer.reset();
        assert!(writer.is_empty());
        assert!(!writer.header_written);
    }
}
