use std::net::SocketAddr;

use super::{Error, Result};
use mullvad_types::{
    constraints::Constraint,
    relay_constraints::{LocationConstraint, Ownership, Providers},
    settings::SettingsVersion,
};
use serde::{Deserialize, Serialize};
use talpid_types::net::{
    Endpoint, TransportProtocol,
    proxy::{CustomProxy, Shadowsocks, ShadowsocksCipher, Socks5Local, Socks5Remote, SocksAuth},
};

// ======================================================
// Section for vendoring types and values that
// this settings version depend on. See `mod.rs`.

// ======================================================
// This is a closed migration.

/// Specifies a specific endpoint or [`BridgeConstraints`] to use when `mullvad-daemon` selects a
/// bridge server.
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct NewBridgeSettings {
    bridge_type: BridgeType,
    normal: BridgeConstraints,
    custom: Option<CustomProxy>,
}

/// Limits the set of bridge servers to use in `mullvad-daemon`.
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(default)]
#[serde(rename_all = "snake_case")]
pub struct BridgeConstraints {
    location: Constraint<LocationConstraint>,
    providers: Constraint<Providers>,
    ownership: Constraint<Ownership>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BridgeType {
    /// Let the relay selection algorithm decide on bridges, based on the relay list
    /// and normal bridge constraints.
    #[default]
    Normal,
    /// Use custom bridge configuration.
    Custom,
}

/// We change bridge settings to no longer be an enum with custom and normal variants. It now is a
/// struct which contains a bridge type, a normal relay constraint and optional custom constraints.
///
/// We also migrate api access methods to use a slightly more shallow CustomProxy implementation
/// that instead of having a Socks5 and Shadowsocks variant instead has a Socks5Local, Socks5Remote
/// and Shadowsocks variant.
///
/// The predefined access methods "Direct" and "Mullvad Bridges" are now stored as distinct keys in
/// the api_access_methods settings, separating them from user-defined access methods in the
/// settings data structure.
///
/// We also take the opportunity to rename a couple of fields that relate to proxy types.
/// We rename
/// - shadowsocks.peer to shadowsocks.endpoint
/// - socks5_remote.authentication to socks5_remote.auth
/// - socks5_remote.peer to socks5_remote.endpoint
pub fn migrate(settings: &mut serde_json::Value) -> Result<()> {
    if !version_matches(settings) {
        return Ok(());
    }

    log::info!("Migrating settings format to V8");

    migrate_bridge_settings(settings)?;

    migrate_api_access_settings(settings)?;

    settings["settings_version"] = serde_json::json!(SettingsVersion::V8);

    Ok(())
}

fn migrate_api_access_settings(settings: &mut serde_json::Value) -> Result<()> {
    if let Some(access_method_settings_list) = settings
        .get_mut("api_access_methods")
        .and_then(|api_access_methods| api_access_methods.get_mut("access_method_settings"))
        .and_then(|access_method_settings| access_method_settings.as_array_mut())
    {
        for access_method_setting in access_method_settings_list {
            let access_method = access_method_setting
                .get_mut("access_method")
                .ok_or(Error::InvalidSettingsContent)?;
            match access_method.get_mut("custom") {
                None => continue,
                Some(custom_access_method) => {
                    if let Some(shadowsocks) = custom_access_method.get_mut("shadowsocks") {
                        rename_field(shadowsocks, "peer", "endpoint")?;
                    } else if let Some(new_socks5_local) = custom_access_method
                        .get("socks5")
                        .and_then(|socks5| socks5.get("local"))
                    {
                        custom_access_method["socks5_local"] = new_socks5_local.clone();
                        custom_access_method
                            .as_object_mut()
                            .ok_or(Error::InvalidSettingsContent)?
                            .remove("socks5");
                    } else if let Some(socks5_remote) = custom_access_method
                        .get("socks5")
                        .and_then(|socks5| socks5.get("remote"))
                    {
                        let mut new_socks5_remote = socks5_remote.clone();
                        rename_field(&mut new_socks5_remote, "authentication", "auth")?;
                        rename_field(&mut new_socks5_remote, "peer", "endpoint")?;

                        custom_access_method["socks5_remote"] = new_socks5_remote;
                        custom_access_method
                            .as_object_mut()
                            .ok_or(Error::InvalidSettingsContent)?
                            .remove("socks5");
                    } else {
                        return Err(Error::InvalidSettingsContent);
                    }
                }
            }
        }
    }

    // Step 1. Rename { "api_access_methods": { "access_method_settings": .. } } to
    // {"api_access_methods": { "custom": .. } }.
    // Step 2. Collect all of the built-in methods from {"api_access_methods": {"custom": [ .. ] } }
    // Step 3. Remove all of the built-in methods from {"api_access_methods": {"custom": [..] } }
    // Step 4. Add the collected built-in methods from step 2 to { "api_access_methods": { .. } }
    // under some appropriate key.
    if let Some(access_method_settings) = settings
        .get_mut("api_access_methods")
        .and_then(serde_json::value::Value::as_object_mut)
    {
        // Step 1.
        rename_map_field(access_method_settings, "access_method_settings", "custom")?;

        if let Some(access_method_settings_list) = access_method_settings
            .get_mut("custom")
            .and_then(serde_json::value::Value::as_array_mut)
        {
            // Step 2.
            let built_ins: Vec<_> = access_method_settings_list
                .iter()
                .filter(|value| {
                    value
                        .get("access_method")
                        .and_then(|value| value.get("built_in"))
                        .is_some()
                })
                .cloned()
                .collect();

            // Step 3.
            for built_in in built_ins.iter() {
                access_method_settings_list
                    .retain(|access_method| access_method.get("id") != built_in.get("id"));
            }

            // Step 4.
            // Note that the only supported built-in access methods at this time
            // are "Direct" and "Mullvad Bridges", so we may discard anything
            // else.
            let built_ins: Vec<_> = built_ins
                .into_iter()
                .filter_map(|built_in| {
                    match built_in
                        .get("access_method")
                        .and_then(|value| value.get("built_in"))
                        .and_then(|value| value.as_str())
                    {
                        Some("direct") => Some(("direct".to_string(), built_in)),
                        Some("bridge") => Some(("mullvad_bridges".to_string(), built_in)),
                        Some(_) | None => None,
                    }
                })
                .collect();

            access_method_settings.extend(built_ins);
        }
    }

    Ok(())
}

fn migrate_bridge_settings(settings: &mut serde_json::Value) -> Result<()> {
    let new = if let Some(custom_bridge_local) = settings
        .get_mut("bridge_settings")
        .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
        .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("local"))
    {
        NewBridgeSettings {
            bridge_type: BridgeType::Custom,
            normal: BridgeConstraints::default(),
            custom: Some(CustomProxy::Socks5Local(Socks5Local {
                remote_endpoint: Endpoint {
                    address: extract_str(custom_bridge_local.get("peer"))?
                        .parse()
                        .map_err(|_| Error::InvalidSettingsContent)?,
                    protocol: TransportProtocol::Tcp,
                },
                local_port: custom_bridge_local
                    .get("port")
                    .ok_or(Error::InvalidSettingsContent)?
                    .as_u64()
                    .ok_or(Error::InvalidSettingsContent)?
                    .try_into()
                    .map_err(|_| Error::InvalidSettingsContent)?,
            })),
        }
    } else if let Some(custom_bridge_remote) = settings
        .get_mut("bridge_settings")
        .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
        .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("remote"))
    {
        NewBridgeSettings {
            bridge_type: BridgeType::Custom,
            normal: BridgeConstraints::default(),
            custom: Some(CustomProxy::Socks5Remote(Socks5Remote {
                endpoint: extract_str(custom_bridge_remote.get("address"))?
                    .parse()
                    .map_err(|_| Error::InvalidSettingsContent)?,
                auth: custom_bridge_remote.get("auth").and_then(|auth| {
                    let username = auth.get("username")?.to_string();
                    let password = auth.get("password")?.to_string();
                    SocksAuth::new(username, password).ok()
                }),
            })),
        }
    } else if let Some(custom_bridge_shadowsocks) = settings
        .get_mut("bridge_settings")
        .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
        .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("shadowsocks"))
    {
        let shadowsocks = {
            let endpoint = extract_str(custom_bridge_shadowsocks.get("peer"))?
                .parse::<SocketAddr>()
                .map_err(|_| Error::InvalidSettingsContent)?;
            let cipher =
                extract_str(custom_bridge_shadowsocks.get("cipher")).and_then(|cipher| {
                    ShadowsocksCipher::new(cipher).or(Err(Error::InvalidSettingsContent))
                })?;
            let password = extract_str(custom_bridge_shadowsocks.get("password"))?.to_string();

            Shadowsocks::new(endpoint, cipher, password)
        };

        NewBridgeSettings {
            bridge_type: BridgeType::Custom,
            normal: BridgeConstraints::default(),
            custom: Some(CustomProxy::Shadowsocks(shadowsocks)),
        }
    } else if let Some(normal_bridge) = settings
        .get_mut("bridge_settings")
        .and_then(|bridge_settings| bridge_settings.get_mut("normal"))
    {
        NewBridgeSettings {
            bridge_type: BridgeType::Normal,
            normal: serde_json::from_value(normal_bridge.clone()).map_err(Error::Serialize)?,
            custom: None,
        }
    } else {
        return Ok(());
    };

    settings["bridge_settings"] = serde_json::json!(new);

    Ok(())
}

fn extract_str(opt: Option<&serde_json::Value>) -> Result<&str> {
    opt.ok_or(Error::InvalidSettingsContent)?
        .as_str()
        .ok_or(Error::InvalidSettingsContent)
}

fn rename_field(value: &mut serde_json::Value, old_name: &str, new_name: &str) -> Result<()> {
    value
        .as_object_mut()
        .ok_or(Error::InvalidSettingsContent)
        .and_then(|object| rename_map_field(object, old_name, new_name))
}

fn rename_map_field(
    object: &mut serde_json::Map<String, serde_json::Value>,
    old_name: &str,
    new_name: &str,
) -> Result<()> {
    let old_value = object
        .get(old_name)
        .ok_or(Error::InvalidSettingsContent)?
        .clone();
    let _ = object.insert(new_name.to_string(), old_value);
    object.remove(old_name);
    Ok(())
}

fn version_matches(settings: &mut serde_json::Value) -> bool {
    settings
        .get("settings_version")
        .map(|version| version == SettingsVersion::V7 as u64)
        .unwrap_or(false)
}

#[cfg(test)]
mod test {
    use crate::migrations::v7::{migrate_api_access_settings, migrate_bridge_settings};

    use super::{migrate, version_matches};

    pub const V7_SETTINGS: &str = r#"
{

  "relay_settings": {
    "normal": {
      "location": {
        "only": {
          "location": {
            "hostname": [
              "ch",
              "zrh",
              "ch-zrh-ovpn-001"
            ]
          }
        }
      },
      "providers": "any",
      "ownership": "any",
      "tunnel_protocol": "any",
      "wireguard_constraints": {
        "port": "any",
        "ip_version": "any",
        "use_multihop": false,
        "entry_location": {
          "only": {
            "location": {
              "country": "se"
            }
          }
        }
      },
      "openvpn_constraints": {
        "port": {
          "only": {
            "protocol": "udp",
            "port": {
              "only": 1195
            }
          }
        }
      }
    }
  },
  "bridge_settings": {
    "custom": {
      "local": {
        "port": 1080,
        "peer": "1.3.3.7:22"
      }
    }
  },
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
        "name": "Direct",
        "enabled": true,
        "access_method": {
          "built_in": "direct"
        }
      },
      {
        "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
        "name": "Mullvad Bridges",
        "enabled": true,
        "access_method": {
          "built_in": "bridge"
        }
      },
      {
        "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
        "name": "localsox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5": {
              "local": {
                "remote_endpoint": {
                  "address": "1.3.3.7:1080",
                  "protocol": "tcp"
                },
                "local_port": 1079
              }
            }
          }
        }
      },
      {
        "id": "1e377232-8a53-4414-8b8f-f487227aaedb",
        "name": "remotesox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5": {
              "remote": {
                "peer": "1.3.3.7:1080",
                "authentication": null
              }
            }
          }
        }
      },
      {
        "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
        "name": "remotess",
        "enabled": true,
        "access_method": {
          "custom": {
            "shadowsocks": {
              "peer": "1.3.3.7:1080",
              "password": "mypass",
              "cipher": "aes-128-cfb"
            }
          }
        }
      }
    ]
  },
  "obfuscation_settings": {
    "selected_obfuscation": "udp2_tcp",
    "udp2tcp": {
      "port": "any"
    }
  },
  "bridge_state": "auto",
  "allow_lan": true,
  "block_when_disconnected": false,
  "auto_connect": false,
  "tunnel_options": {
    "openvpn": {
      "mssfix": null
    },
    "wireguard": {
      "mtu": null,
      "rotation_interval": {
          "secs": 86400,
          "nanos": 0
      },
      "quantum_resistant": "auto"
    },
    "generic": {
      "enable_ipv6": false
    },
    "dns_options": {
      "state": "default",
      "default_options": {
        "block_ads": false,
        "block_trackers": false
      },
      "custom_options": {
        "addresses": [
          "1.1.1.1",
          "1.2.3.4"
        ]
      }
    }
  },
  "settings_version": 7
}
"#;

    pub const V8_SETTINGS: &str = r#"
{

  "relay_settings": {
    "normal": {
      "location": {
        "only": {
          "location": {
            "hostname": [
              "ch",
              "zrh",
              "ch-zrh-ovpn-001"
            ]
          }
        }
      },
      "providers": "any",
      "ownership": "any",
      "tunnel_protocol": "any",
      "wireguard_constraints": {
        "port": "any",
        "ip_version": "any",
        "use_multihop": false,
        "entry_location": {
          "only": {
            "location": {
              "country": "se"
            }
          }
        }
      },
      "openvpn_constraints": {
        "port": {
          "only": {
            "protocol": "udp",
            "port": {
              "only": 1195
            }
          }
        }
      }
    }
  },
  "bridge_settings": {
    "bridge_type": "custom",
    "normal": {
        "location": "any",
        "providers": "any",
        "ownership": "any"
    },
    "custom": {
      "socks5_local": {
        "local_port": 1080,
        "remote_endpoint": {
            "address": "1.3.3.7:22",
            "protocol": "tcp"
        }
      }
    }
  },
  "api_access_methods": {
    "direct": {
      "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
      "name": "Direct",
      "enabled": true,
      "access_method": {
        "built_in": "direct"
      }
    },
    "mullvad_bridges": {
      "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
      "name": "Mullvad Bridges",
      "enabled": true,
      "access_method": {
        "built_in": "bridge"
      }
    },
    "custom": [
      {
        "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
        "name": "localsox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5_local": {
              "remote_endpoint": {
                "address": "1.3.3.7:1080",
                "protocol": "tcp"
              },
              "local_port": 1079
            }
          }
        }
      },
      {
        "id": "1e377232-8a53-4414-8b8f-f487227aaedb",
        "name": "remotesox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5_remote": {
              "endpoint": "1.3.3.7:1080",
              "auth": null
            }
          }
        }
      },
      {
        "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
        "name": "remotess",
        "enabled": true,
        "access_method": {
          "custom": {
            "shadowsocks": {
              "endpoint": "1.3.3.7:1080",
              "password": "mypass",
              "cipher": "aes-128-cfb"
            }
          }
        }
      }
    ]
  },
  "obfuscation_settings": {
    "selected_obfuscation": "udp2_tcp",
    "udp2tcp": {
      "port": "any"
    }
  },
  "bridge_state": "auto",
  "allow_lan": true,
  "block_when_disconnected": false,
  "auto_connect": false,
  "tunnel_options": {
    "openvpn": {
      "mssfix": null
    },
    "wireguard": {
      "mtu": null,
      "rotation_interval": {
          "secs": 86400,
          "nanos": 0
      },
      "quantum_resistant": "auto"
    },
    "generic": {
      "enable_ipv6": false
    },
    "dns_options": {
      "state": "default",
      "default_options": {
        "block_ads": false,
        "block_trackers": false
      },
      "custom_options": {
        "addresses": [
          "1.1.1.1",
          "1.2.3.4"
        ]
      }
    }
  },
  "settings_version": 8
}
"#;

    #[test]
    fn test_v7_to_v8_migration() {
        let mut old_settings = serde_json::from_str(V7_SETTINGS).unwrap();

        assert!(version_matches(&mut old_settings));
        migrate(&mut old_settings).unwrap();
        let new_settings: serde_json::Value = serde_json::from_str(V8_SETTINGS).unwrap();

        assert_eq!(&old_settings, &new_settings);
    }

    #[test]
    fn test_bridge_settings_custom_local_proxy() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "custom": {
      "local": {
        "port": 1080,
        "peer": "1.3.3.7:22"
      }
    }
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "bridge_type": "custom",
    "normal": {
      "location": "any",
      "providers": "any",
      "ownership": "any"
    },
    "custom": {
      "socks5_local": {
        "local_port": 1080,
        "remote_endpoint": {
            "address": "1.3.3.7:22",
            "protocol": "tcp"
        }
      }
    }
  }
}"#,
        )
        .unwrap();

        migrate_bridge_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_bridge_settings_custom_remote_proxy() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "custom": {
      "remote": {
        "address": "1.3.3.7:1080",
        "auth": null
      }
    }
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "bridge_type": "custom",
    "normal": {
      "location": "any",
      "providers": "any",
      "ownership": "any"
    },
    "custom": {
      "socks5_remote": {
        "endpoint": "1.3.3.7:1080",
        "auth": null
      }
    }
  }
}"#,
        )
        .unwrap();

        migrate_bridge_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_bridge_settings_custom_shadowsocks_proxy() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "custom": {
      "shadowsocks": {
        "peer": "1.3.3.7:1080",
        "password": "mypass",
        "cipher": "aes-128-cfb",
        "fwmark": 1836018789
      }
    }
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "bridge_type": "custom",
    "normal": {
      "location": "any",
      "providers": "any",
      "ownership": "any"
    },
    "custom": {
      "shadowsocks": {
        "endpoint": "1.3.3.7:1080",
        "password": "mypass",
        "cipher": "aes-128-cfb"
      }
    }
  }
}"#,
        )
        .unwrap();
        migrate_bridge_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_bridge_settings_normal() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "normal": {
      "location": {
        "only": {
          "location": {
            "country": "se"
          }
        }
      },
      "providers": "any",
      "ownership": "any"
    }
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "bridge_type": "normal",
    "normal": {
      "location": {
        "only": {
          "location": {
            "country": "se"
          }
        }
      },
      "providers": "any",
      "ownership": "any"
    },
    "custom": null
  }
}"#,
        )
        .unwrap();
        migrate_bridge_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_bridge_settings_specific_location() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "normal": {
      "location": {
        "only": {
          "location": {
            "country": "se"
          }
        }
      },
      "providers": "any",
      "ownership": "any"
    }
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "bridge_settings": {
    "bridge_type": "normal",
    "normal": {
      "location": {
        "only": {
          "location": {
            "country": "se"
          }
        }
      },
      "providers": "any",
      "ownership": "any"
    },
    "custom": null
  }
}"#,
        )
        .unwrap();
        migrate_bridge_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_custom_socks5_local() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "5eb9b2ee-f764-47c8-8111-ee95910d0099",
        "name": "mysocks",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5": {
              "local": {
                "remote_endpoint": {
                  "address": "1.3.3.7:22",
                  "protocol": "tcp"
                },
                "local_port": 1080
              }
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "custom": [
      {
        "id": "5eb9b2ee-f764-47c8-8111-ee95910d0099",
        "name": "mysocks",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5_local": {
              "remote_endpoint": {
                "address": "1.3.3.7:22",
                "protocol": "tcp"
              },
              "local_port": 1080
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_custom_socks5_remote() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "8e377232-8a53-4414-8b8f-f487227aaedb",
        "name": "remotesox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5": {
              "remote": {
                "peer": "1.3.3.7:1080",
                "authentication": null
              }
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();
        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "custom": [
      {
        "id": "8e377232-8a53-4414-8b8f-f487227aaedb",
        "name": "remotesox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5_remote": {
              "endpoint": "1.3.3.7:1080",
              "auth": null
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_custom_socks5_shadowsocks() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
        "name": "remotess",
        "enabled": true,
        "access_method": {
          "custom": {
            "shadowsocks": {
              "peer": "1.3.3.7:1080",
              "password": "mypass",
              "cipher": "aes-128-cfb"
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "custom": [
      {
        "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
        "name": "remotess",
        "enabled": true,
        "access_method": {
          "custom": {
            "shadowsocks": {
              "endpoint": "1.3.3.7:1080",
              "password": "mypass",
              "cipher": "aes-128-cfb"
            }
          }
        }
      }
    ]
  }
}"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_extract_direct() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
        "name": "Direct",
        "enabled": true,
        "access_method": {
          "built_in": "direct"
        }
      }
    ]
  }
}
"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "direct": {
      "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
      "name": "Direct",
      "enabled": true,
      "access_method": {
        "built_in": "direct"
      }
    },
    "custom": []
  }
}
"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_extract_mullvad_bridges() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
        "name": "Mullvad Bridges",
        "enabled": true,
        "access_method": {
          "built_in": "bridge"
        }
      }
    ]
  }
}
"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "mullvad_bridges": {
      "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
      "name": "Mullvad Bridges",
      "enabled": true,
      "access_method": {
        "built_in": "bridge"
      }
    },
    "custom": []
  }
}
"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_do_not_extract_custom_methods() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
        "name": "localsox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5": {
              "local": {
                "remote_endpoint": {
                  "address": "1.3.3.7:1080",
                  "protocol": "tcp"
                },
                "local_port": 1079
              }
            }
          }
        }
      }
    ]
  }
}
"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "custom": [
      {
        "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
        "name": "localsox",
        "enabled": false,
        "access_method": {
          "custom": {
            "socks5_local": {
              "remote_endpoint": {
                "address": "1.3.3.7:1080",
                "protocol": "tcp"
              },
              "local_port": 1079
            }
          }
        }
      }
    ]
  }
}
"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }

    #[test]
    fn test_api_access_methods_extract_corrupt_built_in() {
        let mut pre: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "access_method_settings": [
      {
        "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
        "name": "Mullvad Bridges",
        "enabled": true,
        "access_method": {
          "built_in": "some_other_alternative"
        }
      }
    ]
  }
}
"#,
        )
        .unwrap();

        let post: serde_json::Value = serde_json::from_str(
            r#"
{
  "api_access_methods": {
    "custom": []
  }
}
"#,
        )
        .unwrap();

        migrate_api_access_settings(&mut pre).unwrap();
        assert_eq!(pre, post);
    }
}
