use std::collections::HashSet;

use dotenv_core::LineEntry;
use dotenv_schema::{DotEnvSchema, SchemaValueType, ValidateResult};

use super::Check;
use crate::{LintKind, Warning};

pub(crate) struct SchemaViolationChecker<'a> {
    schema: Option<&'a DotEnvSchema>,
    seen_keys: HashSet<String>,
    last_line_number: usize,
}

impl<'a> SchemaViolationChecker<'a> {
    pub fn new(schema: Option<&'a DotEnvSchema>) -> Self {
        Self {
            schema,
            seen_keys: HashSet::new(),
            last_line_number: 0,
        }
    }
}

impl Check for SchemaViolationChecker<'_> {
    fn run(&mut self, line: &LineEntry) -> Option<Warning> {
        let schema = self.schema?;
        self.last_line_number = line.number;
        let key = line.get_key()?;
        self.seen_keys.insert(key.to_string());
        let value = line.get_value()?;

        let Some(entry) = schema.entries.get(key) else {
            if schema.allow_other_keys {
                return None;
            }

            return Some(Warning::new(
                line.number,
                self.name(),
                format!("The {key} key is not defined in the schema"),
            ));
        };

        let ValidateResult::Invalid(ty) = entry.is_valid(value) else {
            return None;
        };

        let message = match ty {
            SchemaValueType::String => {
                format!("The {key} key does not match the regex")
            }
            SchemaValueType::Integer => {
                format!("The {key} key is not an integer")
            }
            SchemaValueType::Float => {
                format!("The {key} key is not a valid float")
            }
            SchemaValueType::Boolean => {
                format!("The {key} key is not a valid boolean")
            }
            SchemaValueType::Url => {
                format!("The {key} key is not a valid URL")
            }
            SchemaValueType::Email => {
                format!("The {key} key is not a valid email address")
            }
        };

        Some(Warning::new(line.number, self.name(), message))
    }

    fn name(&self) -> LintKind {
        LintKind::SchemaViolation
    }

    fn end(&mut self) -> Vec<Warning> {
        let mut warnings = Vec::new();
        if let Some(schema) = self.schema {
            for (key, entry) in &schema.entries {
                if entry.required && !self.seen_keys.contains(key) {
                    warnings.push(Warning::new(
                        self.last_line_number,
                        self.name(),
                        format!("The {key} key is required"),
                    ));
                }
            }
        }
        warnings
    }
}
