use crate::cli::args::Method;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Collection {
    pub id: String,
    pub name: String,
    pub items: Vec<CollectionItem>,
    #[serde(default)]
    pub expanded: bool,
    #[serde(default)]
    pub env_vars: Vec<KVParam>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "type")]
pub enum CollectionItem {
    Folder(Folder),
    Request(Request),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Folder {
    pub id: String,
    pub name: String,
    pub items: Vec<CollectionItem>,
    #[serde(default)]
    pub expanded: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KVParam {
    pub key: String,
    pub value: String,
    pub enabled: bool,
    pub description: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum AuthType {
    None,
    Bearer,
    Basic,
    ApiKey,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct BearerAuth {
    pub token: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct BasicAuth {
    pub username: String,
    pub password: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ApiKeyAuth {
    pub key: String,
    pub value: String,
    pub in_header: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Auth {
    pub selected: AuthType,
    pub bearer: BearerAuth,
    pub basic: BasicAuth,
    pub api_key: ApiKeyAuth,
}

impl Default for AuthType {
    fn default() -> Self {
        Self::None
    }
}

impl Auth {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn bearer(token: String) -> Self {
        Self {
            selected: AuthType::Bearer,
            bearer: BearerAuth { token },
            ..Default::default()
        }
    }

    pub fn basic(username: String, password: String) -> Self {
        Self {
            selected: AuthType::Basic,
            basic: BasicAuth { username, password },
            ..Default::default()
        }
    }

    pub fn api_key(key: String, value: String, in_header: bool) -> Self {
        Self {
            selected: AuthType::ApiKey,
            api_key: ApiKeyAuth {
                key,
                value,
                in_header,
            },
            ..Default::default()
        }
    }

    pub fn auto_select(&mut self) {
        if self.selected != AuthType::None {
            return;
        }

        if !self.bearer.token.is_empty() {
            self.selected = AuthType::Bearer;
        } else if !self.basic.username.is_empty() || !self.basic.password.is_empty() {
            self.selected = AuthType::Basic;
        } else if !self.api_key.key.is_empty() || !self.api_key.value.is_empty() {
            self.selected = AuthType::ApiKey;
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum BodyType {
    None,
    Raw,
    FormData,
    XWwwFormUrlEncoded,
}

impl Default for BodyType {
    fn default() -> Self {
        Self::None
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RawBody {
    pub content: String,
    pub content_type: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct FormDataBody {
    pub items: Vec<KVParam>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RequestBody {
    pub selected: BodyType,
    pub raw: RawBody,
    pub form_data: FormDataBody,
    pub x_www_form_urlencoded: FormDataBody,
}

impl RequestBody {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn raw(content: String, content_type: String) -> Self {
        Self {
            selected: BodyType::Raw,
            raw: RawBody {
                content,
                content_type,
            },
            ..Default::default()
        }
    }

    pub fn form_data(items: Vec<KVParam>) -> Self {
        Self {
            selected: BodyType::FormData,
            form_data: FormDataBody { items },
            ..Default::default()
        }
    }

    pub fn x_www_form_urlencoded(items: Vec<KVParam>) -> Self {
        Self {
            selected: BodyType::XWwwFormUrlEncoded,
            x_www_form_urlencoded: FormDataBody { items },
            ..Default::default()
        }
    }

    pub fn auto_select(&mut self) {
        if self.selected != BodyType::None {
            return;
        }

        if !self.raw.content.is_empty() {
            self.selected = BodyType::Raw;
        } else if !self.form_data.items.is_empty() {
            self.selected = BodyType::FormData;
        } else if !self.x_www_form_urlencoded.items.is_empty() {
            self.selected = BodyType::XWwwFormUrlEncoded;
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Request {
    pub id: String,
    pub name: String,
    pub method: Method,
    pub url: String,
    pub params: Vec<KVParam>,
    pub headers: Vec<KVParam>,
    pub auth: Auth,
    pub body: RequestBody,
    pub pre_request_script: Option<String>,
    pub post_response_script: Option<String>,
}

impl Collection {
    pub fn new(name: String) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name,
            items: Vec::new(),
            expanded: false,
            env_vars: Vec::new(),
        }
    }

    pub fn find_request_mut(&mut self, id: &str) -> Option<&mut Request> {
        for item in &mut self.items {
            if let Some(req) = item.find_request_mut(id) {
                return Some(req);
            }
        }
        None
    }

    pub fn find_request(&self, id: &str) -> Option<&Request> {
        for item in &self.items {
            if let Some(req) = item.find_request(id) {
                return Some(req);
            }
        }
        None
    }

    pub fn find_request_by_name(&self, name: &str) -> Option<&Request> {
        for item in &self.items {
            if let Some(req) = item.find_request_by_name(name) {
                return Some(req);
            }
        }
        None
    }

    pub fn replace_urls_with_placeholder(
        &mut self,
        base_url: &str,
        placeholder: &str,
    ) -> Vec<(String, String)> {
        let mut changed_ids = Vec::new();
        Self::recursive_replace(&mut self.items, base_url, placeholder, &mut changed_ids);
        changed_ids
    }

    pub fn detect_base_url(&self) -> Option<String> {
        let mut urls = Vec::new();
        self.collect_urls(&self.items, &mut urls);

        if urls.is_empty() {
            return None;
        }

        // Find the shortest URL as a starting candidate for common prefix
        let mut shortest = urls.iter().min_by_key(|u| u.len()).unwrap().clone();

        // Refine it to be just the domain part
        if let Some(pos) = shortest.find("://") {
            if let Some(slash_pos) = shortest[pos + 3..].find('/') {
                shortest = shortest[..pos + 3 + slash_pos].to_string();
            }
        }

        // Check if all URLs start with this prefix
        let all_match = urls.iter().all(|u| u.starts_with(&shortest));

        if all_match && shortest.contains("://") {
            Some(shortest)
        } else {
            None
        }
    }

    fn collect_urls(&self, items: &[CollectionItem], urls: &mut Vec<String>) {
        for item in items {
            match item {
                CollectionItem::Request(r) => urls.push(r.url.clone()),
                CollectionItem::Folder(f) => self.collect_urls(&f.items, urls),
            }
        }
    }

    pub fn apply_base_url(&mut self, base_url: &str) {
        // Add or update baseUrl in env_vars
        if let Some(var) = self.env_vars.iter_mut().find(|v| v.key == "baseUrl") {
            var.value = base_url.to_string();
        } else {
            self.env_vars.push(KVParam {
                key: "baseUrl".to_string(),
                value: base_url.to_string(),
                enabled: true,
                description: Some("Auto-generated base URL".to_string()),
            });
        }

        // Replace occurrences in all request URLs
        self.replace_urls_with_placeholder(base_url, "{{baseUrl}}");
    }

    fn recursive_replace(
        items: &mut [CollectionItem],
        base_url: &str,
        placeholder: &str,
        changed: &mut Vec<(String, String)>,
    ) {
        for item in items {
            match item {
                CollectionItem::Request(r) => {
                    if r.url.starts_with(base_url) {
                        r.url = r.url.replace(base_url, placeholder);
                        changed.push((r.id.clone(), r.url.clone()));
                    }
                }
                CollectionItem::Folder(f) => {
                    Self::recursive_replace(&mut f.items, base_url, placeholder, changed)
                }
            }
        }
    }
}

impl Folder {
    pub fn new(name: String) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name,
            items: Vec::new(),
            expanded: false,
        }
    }
}

impl CollectionItem {
    pub fn find_request_mut(&mut self, id: &str) -> Option<&mut Request> {
        match self {
            CollectionItem::Request(req) => {
                if req.id == id {
                    Some(req)
                } else {
                    None
                }
            }
            CollectionItem::Folder(f) => {
                for item in &mut f.items {
                    if let Some(req) = item.find_request_mut(id) {
                        return Some(req);
                    }
                }
                None
            }
        }
    }

    pub fn find_request(&self, id: &str) -> Option<&Request> {
        match self {
            CollectionItem::Request(req) => {
                if req.id == id {
                    Some(req)
                } else {
                    None
                }
            }
            CollectionItem::Folder(f) => {
                for item in &f.items {
                    if let Some(req) = item.find_request(id) {
                        return Some(req);
                    }
                }
                None
            }
        }
    }

    pub fn find_request_by_name(&self, name: &str) -> Option<&Request> {
        match self {
            CollectionItem::Request(req) => {
                if req.name == name {
                    Some(req)
                } else {
                    None
                }
            }
            CollectionItem::Folder(f) => {
                for item in &f.items {
                    if let Some(req) = item.find_request_by_name(name) {
                        return Some(req);
                    }
                }
                None
            }
        }
    }

    #[allow(dead_code)]
    pub fn name(&self) -> &str {
        match self {
            CollectionItem::Folder(f) => &f.name,
            CollectionItem::Request(r) => &r.name,
        }
    }

    #[allow(dead_code)]
    pub fn set_name(&mut self, name: String) {
        match self {
            CollectionItem::Folder(f) => f.name = name,
            CollectionItem::Request(r) => r.name = name,
        }
    }
}

impl Request {
    pub fn new(name: String, method: Method, url: String) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name,
            method,
            url,
            params: Vec::new(),
            headers: Vec::new(),
            auth: Auth::default(),
            body: RequestBody::default(),
            pre_request_script: None,
            post_response_script: None,
        }
    }
}
