use crate::model::{
    ApiKind, ContractMethod, ContractRenderRequest, ContractService, ContractTypedItem,
    ReadReturnKind, ValueType,
};
use std::fmt::{Display, Formatter};

pub const STRING_BYTE_CAPACITY: usize = 32;
pub const STRING_REGISTER_WIDTH: u16 = 17;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AreaKind {
    Coil,
    DiscreteInput,
    HoldingRegister,
    InputRegister,
}

impl AreaKind {
    pub fn c_label(&self) -> &'static str {
        match self {
            Self::Coil => "线圈",
            Self::DiscreteInput => "离散输入",
            Self::HoldingRegister => "保持寄存器",
            Self::InputRegister => "输入寄存器",
        }
    }

    pub fn function_code_label(&self) -> &'static str {
        match self {
            Self::Coil => "0x05/0x0F",
            Self::DiscreteInput => "0x02",
            Self::HoldingRegister => "0x06/0x10",
            Self::InputRegister => "0x04",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MethodWireKind {
    Bit,
    Register,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlannedValue {
    Dto {
        type_name: String,
        items: Vec<ItemPlan>,
    },
    Scalar {
        item: ItemPlan,
    },
    Parameters {
        items: Vec<ItemPlan>,
    },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ItemPlan {
    pub name: String,
    pub summary: Option<String>,
    pub value_type: ValueType,
    pub offset: u16,
    pub width: u16,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodPlan {
    pub api_kind: ApiKind,
    pub interface_name: String,
    pub interface_summary: Option<String>,
    pub method_name: String,
    pub method_summary: Option<String>,
    pub c_method_name: String,
    pub rust_method_name: String,
    pub area_kind: AreaKind,
    pub wire_kind: MethodWireKind,
    pub start_address: u16,
    pub quantity: u16,
    pub planned_value: PlannedValue,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContractPlan {
    pub services: Vec<ContractService>,
    pub methods: Vec<MethodPlan>,
    pub total_coils: u16,
    pub total_discrete_inputs: u16,
    pub total_holding_registers: u16,
    pub total_input_registers: u16,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContractPlanError {
    message: String,
}

impl ContractPlanError {
    fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

impl Display for ContractPlanError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl std::error::Error for ContractPlanError {}

pub fn build_plan(request: &ContractRenderRequest) -> Result<ContractPlan, ContractPlanError> {
    if request.transports.is_empty() {
        return Err(ContractPlanError::new("至少选择一种传输类型"));
    }
    if request.services.is_empty() {
        return Err(ContractPlanError::new("至少定义一个 Api 服务"));
    }

    let mut coil_cursor = 0_u16;
    let mut discrete_input_cursor = 0_u16;
    let mut holding_register_cursor = 0_u16;
    let mut input_register_cursor = 0_u16;
    let mut methods = Vec::new();

    for service in &request.services {
        if service.methods.is_empty() {
            return Err(ContractPlanError::new(format!(
                "服务 {} 至少需要一个方法",
                service.interface_name
            )));
        }
        for method in &service.methods {
            let normalized_interface_name = normalize_type_name(&service.interface_name);
            let c_method_name = to_snake_case(&method.method_name);
            let rust_method_name = c_method_name.clone();
            let (area_kind, wire_kind, planned_value) = plan_method_value(service, method)?;
            let quantity = planned_value_width(&planned_value);
            let start_address = match area_kind {
                AreaKind::Coil => {
                    let start = coil_cursor;
                    coil_cursor = checked_next_cursor(coil_cursor, quantity, "线圈")?;
                    start
                }
                AreaKind::DiscreteInput => {
                    let start = discrete_input_cursor;
                    discrete_input_cursor =
                        checked_next_cursor(discrete_input_cursor, quantity, "离散输入")?;
                    start
                }
                AreaKind::HoldingRegister => {
                    let start = holding_register_cursor;
                    holding_register_cursor =
                        checked_next_cursor(holding_register_cursor, quantity, "保持寄存器")?;
                    start
                }
                AreaKind::InputRegister => {
                    let start = input_register_cursor;
                    input_register_cursor =
                        checked_next_cursor(input_register_cursor, quantity, "输入寄存器")?;
                    start
                }
            };
            methods.push(MethodPlan {
                api_kind: service.api_kind.clone(),
                interface_name: normalized_interface_name,
                interface_summary: trim_option(&service.interface_summary),
                method_name: method.method_name.trim().to_string(),
                method_summary: trim_option(&method.summary),
                c_method_name,
                rust_method_name,
                area_kind,
                wire_kind,
                start_address,
                quantity,
                planned_value,
            });
        }
    }

    Ok(ContractPlan {
        services: request.services.clone(),
        methods,
        total_coils: coil_cursor,
        total_discrete_inputs: discrete_input_cursor,
        total_holding_registers: holding_register_cursor,
        total_input_registers: input_register_cursor,
    })
}

fn checked_next_cursor(cursor: u16, quantity: u16, label: &str) -> Result<u16, ContractPlanError> {
    cursor
        .checked_add(quantity)
        .ok_or_else(|| ContractPlanError::new(format!("{label}地址规划超出 16 位地址上限")))
}

fn plan_method_value(
    service: &ContractService,
    method: &ContractMethod,
) -> Result<(AreaKind, MethodWireKind, PlannedValue), ContractPlanError> {
    let method_name = method.method_name.trim();
    if method_name.is_empty() {
        return Err(ContractPlanError::new("方法名不能为空"));
    }

    match service.api_kind {
        ApiKind::Write => {
            if method.parameters.is_empty() {
                return Err(ContractPlanError::new(format!(
                    "写方法 {method_name} 至少需要一个参数"
                )));
            }
            let bit_only = method
                .parameters
                .iter()
                .all(|parameter| matches!(parameter.value_type, ValueType::Boolean));
            let items = build_item_plans(&method.parameters, bit_only)?;
            let planned_value = PlannedValue::Parameters { items };
            Ok((
                if bit_only {
                    AreaKind::Coil
                } else {
                    AreaKind::HoldingRegister
                },
                if bit_only {
                    MethodWireKind::Bit
                } else {
                    MethodWireKind::Register
                },
                planned_value,
            ))
        }
        ApiKind::StaticRead | ApiKind::RuntimeRead => {
            let read_return_kind = method
                .read_return_kind
                .clone()
                .unwrap_or(ReadReturnKind::Dto);
            let planned_value = match read_return_kind {
                ReadReturnKind::Dto => {
                    if method.read_fields.is_empty() {
                        return Err(ContractPlanError::new(format!(
                            "读方法 {method_name} 选择 DTO 返回时至少需要一个字段"
                        )));
                    }
                    let dto_type_name = normalize_type_name(
                        method
                            .read_return_type_name
                            .as_deref()
                            .unwrap_or(method_name),
                    );
                    let bit_only = method
                        .read_fields
                        .iter()
                        .all(|field| matches!(field.value_type, ValueType::Boolean));
                    let items = build_item_plans(&method.read_fields, bit_only)?;
                    (
                        if matches!(service.api_kind, ApiKind::RuntimeRead) && bit_only {
                            AreaKind::DiscreteInput
                        } else {
                            AreaKind::InputRegister
                        },
                        if bit_only {
                            MethodWireKind::Bit
                        } else {
                            MethodWireKind::Register
                        },
                        PlannedValue::Dto {
                            type_name: dto_type_name,
                            items,
                        },
                    )
                }
                other => {
                    let scalar_type = read_return_kind_to_value_type(&other);
                    let bit_only = matches!(scalar_type, ValueType::Boolean)
                        && matches!(service.api_kind, ApiKind::RuntimeRead);
                    let item = ItemPlan {
                        name: "value".to_string(),
                        summary: trim_option(&method.summary),
                        value_type: scalar_type.clone(),
                        offset: 0,
                        width: value_type_width(&scalar_type, bit_only),
                    };
                    (
                        if bit_only {
                            AreaKind::DiscreteInput
                        } else {
                            AreaKind::InputRegister
                        },
                        if bit_only {
                            MethodWireKind::Bit
                        } else {
                            MethodWireKind::Register
                        },
                        PlannedValue::Scalar { item },
                    )
                }
            };
            Ok(planned_value)
        }
    }
}

fn build_item_plans(
    items: &[ContractTypedItem],
    bit_only: bool,
) -> Result<Vec<ItemPlan>, ContractPlanError> {
    let mut offset = 0_u16;
    let mut planned = Vec::with_capacity(items.len());
    for item in items {
        let item_name = item.name.trim();
        if item_name.is_empty() {
            return Err(ContractPlanError::new("字段或参数名不能为空"));
        }
        let width = value_type_width(&item.value_type, bit_only);
        planned.push(ItemPlan {
            name: to_snake_case(item_name),
            summary: trim_option(&item.summary),
            value_type: item.value_type.clone(),
            offset,
            width,
        });
        offset = checked_next_cursor(offset, width, "方法块")?;
    }
    Ok(planned)
}

fn planned_value_width(value: &PlannedValue) -> u16 {
    match value {
        PlannedValue::Dto { items, .. } | PlannedValue::Parameters { items } => items
            .iter()
            .map(|item| item.width)
            .fold(0_u16, |summary, width| summary.saturating_add(width)),
        PlannedValue::Scalar { item } => item.width,
    }
}

fn value_type_width(value_type: &ValueType, bit_only: bool) -> u16 {
    if bit_only {
        return 1;
    }
    match value_type {
        ValueType::Boolean => 1,
        ValueType::Int => 2,
        ValueType::String | ValueType::Bytes => STRING_REGISTER_WIDTH,
    }
}

fn read_return_kind_to_value_type(read_return_kind: &ReadReturnKind) -> ValueType {
    match read_return_kind {
        ReadReturnKind::Boolean => ValueType::Boolean,
        ReadReturnKind::Int => ValueType::Int,
        ReadReturnKind::String => ValueType::String,
        ReadReturnKind::Bytes => ValueType::Bytes,
        ReadReturnKind::Dto => ValueType::Bytes,
    }
}

pub fn trim_option(value: &Option<String>) -> Option<String> {
    value.as_ref().and_then(|item| {
        let normalized = item.trim();
        if normalized.is_empty() {
            None
        } else {
            Some(normalized.to_string())
        }
    })
}

pub fn normalize_type_name(raw: &str) -> String {
    let words = split_words(raw);
    if words.is_empty() {
        return "GeneratedType".to_string();
    }
    words
        .into_iter()
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                Some(first) => {
                    let mut normalized = String::new();
                    normalized.extend(first.to_uppercase());
                    normalized.push_str(chars.as_str());
                    normalized
                }
                None => String::new(),
            }
        })
        .collect::<Vec<_>>()
        .join("")
}

pub fn to_upper_snake_case(raw: &str) -> String {
    let words = split_words(raw);
    if words.is_empty() {
        return "GENERATED_ITEM".to_string();
    }
    words
        .into_iter()
        .map(|word| word.to_uppercase())
        .collect::<Vec<_>>()
        .join("_")
}

pub fn to_snake_case(raw: &str) -> String {
    let words = split_words(raw);
    if words.is_empty() {
        return "generated_item".to_string();
    }
    let normalized = words
        .into_iter()
        .map(|word| word.to_lowercase())
        .collect::<Vec<_>>()
        .join("_");
    if normalized
        .chars()
        .next()
        .map(|character| character.is_ascii_digit())
        .unwrap_or(false)
    {
        format!("generated_{normalized}")
    } else {
        normalized
    }
}

fn split_words(raw: &str) -> Vec<String> {
    let mut words = Vec::new();
    let mut current = String::new();
    let mut previous_is_lower_or_digit = false;

    for character in raw.chars() {
        if !character.is_ascii_alphanumeric() {
            if !current.is_empty() {
                words.push(current.clone());
                current.clear();
            }
            previous_is_lower_or_digit = false;
            continue;
        }

        let is_boundary = character.is_ascii_uppercase() && previous_is_lower_or_digit;
        if is_boundary && !current.is_empty() {
            words.push(current.clone());
            current.clear();
        }

        current.push(character);
        previous_is_lower_or_digit = character.is_ascii_lowercase() || character.is_ascii_digit();
    }

    if !current.is_empty() {
        words.push(current);
    }

    words
}
