pub use crate::resources::{common::Entity, location::*};
use crate::{
    error::{Error, GoogleResponse},
    resources::{
        bucket_access_control::{BucketAccessControl, NewBucketAccessControl},
        common::ListResponse,
        default_object_access_control::{
            DefaultObjectAccessControl, NewDefaultObjectAccessControl,
        },
    },
};

/// The Buckets resource represents a
/// [bucket](https://cloud.google.com/storage/docs/key-terms#buckets) in Google Cloud Storage. There
/// is a single global namespace shared by all buckets. For more information, see
/// [Bucket Name Requirements](https://cloud.google.com/storage/docs/naming#requirements).
///
/// Buckets contain objects which can be accessed by their own methods. In addition to the
/// [ACL property](https://cloud.google.com/storage/docs/access-control/lists), buckets contain
/// `BucketAccessControls`, for use in fine-grained manipulation of an existing bucket's access
/// controls.
///
/// A bucket is always owned by the project team owners group.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Bucket {
    /// The kind of item this is. For buckets, this is always `storage#bucket`.
    pub kind: String,
    /// The ID of the bucket. For buckets, the `id` and `name` properties are the same.
    pub id: String, // should be u64, mumble mumble
    /// The URI of this bucket.
    pub self_link: String,
    /// The project number of the project the bucket belongs to.
    #[serde(deserialize_with = "crate::from_str")]
    pub project_number: u64,
    /// The name of the bucket.
    pub name: String,
    /// The creation time of the bucket in RFC 3339 format.
    pub time_created: chrono::DateTime<chrono::Utc>,
    /// The modification time of the bucket in RFC 3339 format.
    pub updated: chrono::DateTime<chrono::Utc>,
    /// Whether or not to automatically apply an eventBasedHold to new objects added to the bucket.
    pub default_event_based_hold: Option<bool>,
    /// The bucket's retention policy, which defines the minimum age an object in the bucket must
    /// reach before it can be deleted or overwritten.
    pub retention_policy: Option<RetentionPolicy>,
    /// The metadata generation of this bucket.
    #[serde(deserialize_with = "crate::from_str")]
    pub metageneration: i64,
    /// Access controls on the bucket, containing one or more bucketAccessControls Resources. If
    /// iamConfiguration.uniformBucketLevelAccess.enabled is set to true, this field is omitted in
    /// responses, and requests that specify this field fail with a 400 Bad Request response.
    pub acl: Option<Vec<BucketAccessControl>>,
    /// Default access controls to apply to new objects when no ACL is provided. This list contains
    /// one or more defaultObjectAccessControls Resources. If
    /// iamConfiguration.uniformBucketLevelAccess.enabled is set to true, this field is omitted in
    /// responses, and requests that specify this field fail.
    pub default_object_acl: Option<Vec<DefaultObjectAccessControl>>,
    /// The bucket's IAM configuration.
    pub iam_configuration: IamConfiguration,
    /// Encryption configuration for a bucket.
    pub encryption: Option<Encryption>,
    /// The owner of the bucket. This is always the project team's owner group.
    pub owner: Option<Owner>,
    /// The location of the bucket. Object data for objects in the bucket resides in physical
    /// storage within this region. Defaults to US. See Cloud Storage bucket locations for the
    /// authoritative list.
    pub location: Location,
    /// The type of location that the bucket resides in, as determined by the location property.
    pub location_type: String,
    /// The bucket's website configuration, controlling how the service behaves when accessing
    /// bucket contents as a web site. See the Static Website Examples for more information.
    pub website: Option<Website>,
    /// The bucket's logging configuration, which defines the destination bucket and optional name
    /// prefix for the current bucket's logs.
    pub logging: Option<Logging>,
    /// The bucket's versioning configuration.
    pub versioning: Option<Versioning>,
    /// The bucket's Cross-Origin Resource Sharing (CORS) configuration.
    pub cors: Option<Vec<Cors>>,
    /// The bucket's lifecycle configuration. See
    /// [lifecycle management](https://cloud.google.com/storage/docs/lifecycle) for more
    /// information.
    pub lifecycle: Option<Lifecycle>,
    /// User-provided bucket labels, in key/value pairs.
    pub labels: Option<std::collections::HashMap<String, String>>,
    /// The bucket's default storage class, used whenever no storageClass is specified for a
    /// newly-created object. If storageClass is not specified when the bucket
    /// is created, it defaults to STANDARD. For more information, see storage classes.
    pub storage_class: StorageClass,
    /// The bucket's billing configuration.
    pub billing: Option<Billing>,
    /// HTTP 1.1 [Entity tag](https://tools.ietf.org/html/rfc7232#section-2.3) for the bucket.
    pub etag: String,
}

/// A model that can be used to insert new buckets into Google Cloud Storage.
#[derive(Debug, PartialEq, Default, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewBucket {
    /// The name of the bucket. See the bucket naming guidelines for more information.
    pub name: String,
    /// Whether or not to automatically apply an eventBasedHold to new objects added to the bucket.
    pub default_event_based_hold: Option<bool>,
    /// Access controls on the bucket, containing one or more `BucketAccessControls` resources. If
    /// `iamConfiguration.uniformBucketLevelAccess.enabled` is set to true, this field is omitted in
    /// responses, and requests that specify this field fail with a `400 Bad Request` response.
    pub acl: Option<Vec<NewBucketAccessControl>>,
    /// Default access controls to apply to new objects when no ACL is provided. This list defines
    /// an entity and role for one or more `DefaultObjectAccessControls` resources. If
    /// `iamConfiguration.uniformBucketLevelAccess.enabled` is set to true, this field is omitted in
    /// responses, and requests that specify this field fail with a `400 Bad Request` response.
    pub default_object_acl: Option<Vec<NewDefaultObjectAccessControl>>,
    /// The bucket's IAM configuration.
    pub iam_configuration: Option<IamConfiguration>,
    /// Encryption configuration for a bucket.
    pub encryption: Option<Encryption>,
    /// The location of the bucket. Object data for objects in the bucket resides in physical
    /// storage within this region. Defaults to US. See Cloud Storage bucket locations for the
    /// authoritative list.
    pub location: Location,
    /// The bucket's website configuration, controlling how the service behaves when accessing
    /// bucket contents as a web site. See the Static Website Examples for more information.
    pub website: Option<Website>,
    /// The bucket's logging configuration, which defines the destination bucket and optional name
    /// prefix for the current bucket's logs.
    pub logging: Option<Logging>,
    /// The bucket's versioning configuration.
    pub versioning: Option<Versioning>,
    /// The bucket's Cross-Origin Resource Sharing (CORS) configuration.
    pub cors: Option<Vec<Cors>>,
    /// The bucket's lifecycle configuration. See
    /// [lifecycle management](https://cloud.google.com/storage/docs/lifecycle) for more
    /// information.
    pub lifecycle: Option<Lifecycle>,
    /// User-provided bucket labels, in key/value pairs.
    pub labels: Option<std::collections::HashMap<String, String>>,
    /// The bucket's default storage class, used whenever no storageClass is specified for a
    /// newly-created object. If storageClass is not specified when the bucket
    /// is created, it defaults to STANDARD. For more information, see storage classes.
    pub storage_class: Option<StorageClass>,
    /// The bucket's billing configuration.
    pub billing: Option<Billing>,
}

/// Contains information about how files are kept after deletion.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RetentionPolicy {
    /// The period of time, in seconds, that objects in the bucket must be retained and cannot be
    /// deleted, overwritten, or made noncurrent. The value must be greater than 0 seconds and less
    /// than 3,155,760,000 seconds.
    #[serde(deserialize_with = "crate::from_str")]
    pub retention_period: u64,
    /// The time from which the retentionPolicy was effective, in RFC 3339 format.
    pub effective_time: chrono::DateTime<chrono::Utc>,
    /// Whether or not the retentionPolicy is locked. If true, the retentionPolicy cannot be removed
    /// and the retention period cannot be reduced.
    pub is_locked: Option<bool>,
}

/// Contains information about the Buckets IAM configuration.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IamConfiguration {
    /// The bucket's uniform bucket-level access configuration.
    ///
    /// Note: iamConfiguration also includes the bucketPolicyOnly field, which uses a legacy name
    /// but has the same functionality as the uniformBucketLevelAccess field. We recommend only
    /// using uniformBucketLevelAccess, as specifying both fields may result in unreliable behavior.
    pub uniform_bucket_level_access: UniformBucketLevelAccess,
}

/// Access that is configured for all objects in one go.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UniformBucketLevelAccess {
    /// Whether or not the bucket uses uniform bucket-level access. If set, access checks only use
    /// bucket-level IAM policies or above.
    pub enabled: bool,
    /// The deadline time for changing iamConfiguration.uniformBucketLevelAccess.enabled from true
    /// to false, in RFC 3339 format.
    ///
    /// iamConfiguration.uniformBucketLevelAccess.enabled may be changed from true to false until
    /// the locked time, after which the field is immutable.
    pub locked_time: Option<chrono::DateTime<chrono::Utc>>,
}

/// Contains information about the encryption used for data in this Bucket.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Encryption {
    /// A Cloud KMS key that will be used to encrypt objects inserted into this bucket, if no
    /// encryption method is specified.
    pub default_kms_key_name: String,
}

/// Contains information about an entity that is able to own a `Bucket`.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Owner {
    /// The entity, in the form project-owner-projectId.
    pub entity: Entity,
    /// The ID for the entity.
    pub entity_id: Option<String>,
}

/// Contains configuration about how to visit the website linked to this Bucket.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Website {
    /// If the requested object path is missing, the service will ensure the path has a trailing
    /// '/', append this suffix, and attempt to retrieve the resulting object. This allows the
    /// creation of index.html objects to represent directory pages.
    pub main_page_suffix: String,
    /// If the requested object path is missing, and any mainPageSuffix object is missing, if
    /// applicable, the service will return the named object from this bucket as the content for a
    /// 404 Not Found result.
    pub not_found_page: String,
}

/// Contains information of where and how access logs to this bucket are maintained.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Logging {
    /// The destination bucket where the current bucket's logs should be placed.
    pub log_bucket: String,
    /// A prefix for log object names. The default prefix is the bucket name.
    pub log_object_prefix: String,
}

/// Contains information about whether a Bucket keeps track of its version.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Versioning {
    /// While set to true, versioning is fully enabled for this bucket.
    pub enabled: bool,
}

/// Contains information about how OPTIONS requests for this Bucket are handled.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cors {
    /// The list of Origins eligible to receive CORS response headers. Note: "*" is permitted in the
    /// list of origins, and means "any Origin".
    pub origin: Vec<String>,
    /// The list of HTTP methods on which to include CORS response headers, (GET, OPTIONS, POST,
    /// etc) Note: "*" is permitted in the list of methods, and means "any method".
    pub method: Vec<String>,
    /// The list of HTTP headers other than the simple response headers to give permission for the
    /// user-agent to share across domains.
    pub response_header: Vec<String>,
    /// The value, in seconds, to return in the Access-Control-Max-Age header used in preflight
    /// responses.
    #[serde(deserialize_with = "crate::from_str")]
    pub max_age_seconds: i32,
}

/// Contains a set of `Rule` Objects which together describe the way this lifecycle behaves
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Lifecycle {
    /// A lifecycle management rule, which is made of an action to take and the condition(s) under
    /// which the action will be taken.
    pub rule: Vec<Rule>,
}

/// An element of the lifecyle list.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Rule {
    /// The action to take.
    pub action: Action,
    /// The condition(s) under which the action will be taken.
    pub condition: Condition,
}

/// Represents an action that might be undertaken due to a `Condition`.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Action {
    /// Type of the action.
    pub r#type: ActionType,
    /// Target storage class. Required iff the type of the action is SetStorageClass.
    pub storage_class: Option<StorageClass>,
}

/// Type of the action.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ActionType {
    /// Deletes a Bucket.
    Delete,
    /// Sets the `storage_class` of a Bucket.
    SetStorageClass,
}

/// A rule that might induce an `Action` if met.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Condition {
    /// Age of an object (in days). This condition is satisfied when an object reaches the specified
    /// age.
    pub age: Option<i32>,
    /// A date in `RFC 3339` format with only the date part (for instance, "2013-01-15"). This
    /// condition is satisfied when an object is created before midnight of the specified date in
    /// UTC.
    pub created_before: Option<chrono::DateTime<chrono::Utc>>,
    /// Relevant only for versioned objects. If the value is true, this condition matches the live
    /// version of objects; if the value is `false`, it matches noncurrent versions of objects.
    pub is_live: Option<bool>,
    /// Objects having any of the storage classes specified by this condition will be matched.
    /// Values include STANDARD, NEARLINE, COLDLINE, MULTI_REGIONAL, REGIONAL, and
    /// DURABLE_REDUCED_AVAILABILITY.
    pub matches_storage_class: Option<Vec<String>>,
    /// Relevant only for versioned objects. If the value is N, this condition is satisfied when
    /// there are at least N versions (including the live version) newer than this version of the
    /// object.
    #[serde(default, deserialize_with = "crate::from_str_opt")]
    pub num_newer_versions: Option<i32>,
}

/// Contains information about the payment structure of this bucket
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Billing {
    /// When set to true, Requester Pays is enabled for this bucket.
    pub requester_pays: bool,
}

/// The type of storage that is used. Pertains to availability, performance and cost.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StorageClass {
    /// Standard Storage is best for data that is frequently accessed ("hot" data) and/or stored for
    /// only brief periods of time.
    Standard,
    /// Nearline Storage is a low-cost, highly durable storage service for storing infrequently
    /// accessed data.
    Nearline,
    /// Coldline Storage is a very-low-cost, highly durable storage service for data archiving,
    /// online backup, and disaster recovery.
    Coldline,
    /// Equivalent to Standard Storage, except Multi-Regional Storage can only be used for objects
    /// stored in multi-regions or dual-regions.
    MultiRegional,
    /// Equivalent to Standard Storage, except Regional Storage can only be used for objects stored
    /// in regions.
    Regional,
    /// Similar to Standard Storage except:
    ///
    /// DRA has higher pricing for operations.
    /// DRA has lower performance, particularly in terms of availability (DRA has a 99% availability
    /// SLA).
    ///
    /// You can move your data from DRA to other storage classes by performing a storage transfer.
    DurableReducedAvailability,
}

/// A representation of the IAM Policiy for a certain bucket.
#[derive(Debug, PartialEq, Default, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IamPolicy {
    /// The [Cloud IAM policy](https://cloud.google.com/iam/docs/policies#versions) version.
    pub version: i32,
    /// The kind of item this is. For policies, this field is ignored in a request and is
    /// `storage#policy` in a response.
    pub kind: Option<String>,
    /// The ID of the resource to which this policy belongs. The response for this field is of the
    /// form `projects/_/buckets/bucket`. This field is ignored in a request.
    pub resource_id: Option<String>,
    /// A list of the bindings for this policy.
    pub bindings: Vec<Binding>,
    /// HTTP 1.1 [Entity tag](https://tools.ietf.org/html/rfc7232#section-2.3) for this policy.
    pub etag: String,
}

/// An association between a role, which comes with a set of permissions, and members who may assume
/// that role.
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Binding {
    /// The role to which members belong. Two types of roles are supported: standard IAM roles,
    /// which grant permissions that do not map directly to those provided by ACLs, and legacy IAM
    /// roles, which do map directly to ACL permissions. All roles are of the format
    /// `roles/storage.specificRole.`
    ///
    /// See
    /// [Cloud Storage IAM Roles](https://cloud.google.com/storage/docs/access-control/iam-roles)
    /// for a list of available roles.
    pub role: IamRole,
    /// A collection of identifiers for members who may assume the provided role. Recognized
    /// identifiers are as follows:
    ///
    /// * `allUsers` — A special identifier that represents anyone on the internet; with or without
    ///   a Google account.
    /// * `allAuthenticatedUsers` — A special identifier that represents anyone who is authenticated
    ///   with a Google account or a service account.
    /// * `user:emailid` — An email address that represents a specific account. For example,
    ///   user:alice@gmail.com or user:joe@example.com.
    /// * `serviceAccount:emailid` — An email address that represents a service account. For
    ///   example, serviceAccount:my-other-app@appspot.gserviceaccount.com .
    /// * `group:emailid` — An email address that represents a Google group. For example,
    ///   group:admins@example.com.
    /// * `domain:domain` — A G Suite domain name that represents all the users of that domain. For
    ///   example, domain:google.com or domain:example.com.
    /// * `projectOwner:projectid` — Owners of the given project. For example,
    ///   projectOwner:my-example-project
    /// * `projectEditor:projectid` — Editors of the given project. For example,
    ///   projectEditor:my-example-project
    /// * `projectViewer:projectid` — Viewers of the given project. For example,
    ///   projectViewer:my-example-project
    pub members: Vec<String>,
    /// A condition object associated with this binding. Each role binding can only contain one
    /// condition.
    pub condition: Option<IamCondition>,
}

/// A condition object associated with a binding.
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IamCondition {
    /// Title of the condition. For example, "expires_end_of_2018".
    pub title: String,
    /// Optional description of the condition. For example, "Expires at midnight on 2018-12-31".
    pub description: Option<String>,
    /// [Attribute-based](https://cloud.google.com/iam/docs/conditions-overview#attributes) logic
    /// expression using a subset of the Common Expression Language (CEL). For example,
    /// "request.time < timestamp('2019-01-01T00:00:00Z')".
    pub expression: String,
}

/// All possible roles that can exist in the IAM system. For a more comprehensive version, check
/// [Googles Documentation](https://cloud.google.com/storage/docs/access-control/iam-roles).
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum IamRole {
    /// Standard roles can be applied to either buckets or projects.
    Standard(StandardIamRole),
    /// Primitive roles are roles that must be added on a per-project basis.
    Primitive(PrimitiveIamRole),
    /// Legacy roles are roles that can only be added to an individual bucket.
    Legacy(LegacyIamRole),
}

/// The following enum contains Cloud Identity and Access Management (Cloud IAM) roles that are
/// associated with Cloud Storage and lists the permissions that are contained in each role. Unless
/// otherwise noted, these roles can be applied either to entire projects or specific buckets.
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum StandardIamRole {
    /// Allows users to create objects. Does not give permission to view, delete, or overwrite
    /// objects.
    #[serde(rename = "roles/storage.objectCreator")]
    ObjectCreator,
    /// Grants access to view objects and their metadata, excluding ACLs.
    ///
    /// Can also list the objects in a bucket.
    #[serde(rename = "roles/storage.objectViewer")]
    ObjectViewer,
    /// Grants full control over objects, including listing, creating, viewing, and deleting
    /// objects.
    #[serde(rename = "roles/storage.objectAdmin")]
    ObjectAdmin,
    /// Full control over HMAC keys in a project.
    #[serde(rename = "roles/storage.hmacKeyAdmin")]
    HmacKeyAdmin,
    /// Grants full control of buckets and objects.
    ///
    /// When applied to an individual bucket, control applies only to the specified bucket and
    /// objects within the bucket.
    #[serde(rename = "roles/storage.admin")]
    Admin,
}

/// The following enum contains primitive roles and the Cloud Storage permissions that these roles
/// contain. Primitive roles cannot be added at the bucket-level.
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum PrimitiveIamRole {
    /// Grants permission to list buckets as well as view bucket metadata, excluding ACLs, when
    /// listing. Also grants permission to list and get HMAC keys in the project.
    #[serde(rename = "role/viewer")]
    Viewer,
    /// Grants permission to create, list, and delete buckets. Grants permission to view bucket
    /// metadata, excluding ACLs, when listing. Grants full control over HMAC keys in a project.
    #[serde(rename = "role/editor")]
    Editor,
    /// Grants permission to create, list, and delete buckets. Also grants permission to view bucket
    /// metadata, excluding ACLs, when listing. Grants full control over HMAC keys in a project.
    #[serde(rename = "role/owner")]
    Owner,
}

/// The following enum contains Cloud IAM roles that are equivalent to Access Control List (ACL)
/// permissions. These Cloud IAM roles can only be applied to a bucket, not a project.
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum LegacyIamRole {
    /// Grants permission to view objects and their metadata, excluding ACLs.
    #[serde(rename = "roles/storage.legacyObjectReader")]
    LegacyObjectReader,
    /// Grants permission to view and edit objects and their metadata, including ACLs.
    #[serde(rename = "roles/storage.legacyObjectOwner")]
    LegacyObjectOwner,
    /// Grants permission to list a bucket's contents and read bucket metadata, excluding Cloud IAM
    /// policies. Also grants permission to read object metadata, excluding Cloud IAM policies, when
    /// listing objects.
    ///
    /// Use of this role is also reflected in the bucket's ACLs. See
    /// [Cloud IAM relation to ACLs](https://cloud.google.com/storage/docs/access-control/iam#acls)
    /// for  more information.
    #[serde(rename = "roles/storage.legacyBucketReader")]
    LegacyBucketReader,
    /// Grants permission to create, overwrite, and delete objects; list objects in a bucket and
    /// read object metadata, excluding Cloud IAM policies, when listing; and read bucket metadata,
    /// excluding Cloud IAM policies.
    ///
    /// Use of this role is also reflected in the bucket's ACLs. See
    /// [Cloud IAM relation to ACLs](https://cloud.google.com/storage/docs/access-control/iam#acls)
    /// for  more information.
    #[serde(rename = "roles/storage.legacyBucketWriter")]
    LegacyBucketWriter,
    /// Grants permission to create, overwrite, and delete objects; list objects in a bucket and
    /// read object metadata, excluding Cloud IAM policies, when listing; and read and edit bucket
    /// metadata, including Cloud IAM policies.
    ///
    /// Use of this role is also reflected in the bucket's ACLs. See
    /// [Cloud IAM relation to ACLs](https://cloud.google.com/storage/docs/access-control/iam#acls)
    /// for  more information.
    #[serde(rename = "roles/storage.legacyBucketOwner")]
    LegacyBucketOwner,
}

/// The request needed to perform the Object::test_iam_permission function.
#[derive(Debug, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TestIamPermission {
    /// The kind of item this is.
    kind: String,
    /// The permissions held by the caller. Permissions are always of the format
    /// `storage.resource.capability`, where resource is one of buckets or objects. See
    /// [Cloud Storage IAM Permissions]
    /// (https://cloud.google.com/storage/docs/access-control/iam-permissions) for a list of
    /// supported permissions.
    permissions: Vec<String>,
}

impl Bucket {
    /// Creates a new `Bucket`. There are many options that you can provide for creating a new
    /// bucket, so the `NewBucket` resource contains all of them. Note that `NewBucket` implements
    /// `Default`, so you don't have to specify the fields you're not using. And error is returned
    /// if that bucket name is already taken.
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::bucket::{Bucket, NewBucket};
    /// use cloud_storage::bucket::{Location, MultiRegion};
    ///
    /// let new_bucket = NewBucket {
    ///    name: "cloud-storage-rs-doc-1".to_string(), // this is the only mandatory field
    ///    location: Location::Multi(MultiRegion::Eu),
    ///    ..Default::default()
    /// };
    /// let bucket = Bucket::create(&new_bucket).await?;
    /// # bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn create(new_bucket: &NewBucket) -> crate::Result<Self> {
        let url = format!("{}/b/", crate::BASE_URL);
        let project = &crate::SERVICE_ACCOUNT.project_id;
        let query = [("project", project)];
        let result: GoogleResponse<Self> = crate::CLIENT
            .post(&url)
            .headers(crate::get_headers().await?)
            .query(&query)
            .json(new_bucket)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::create`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn create_sync(new_bucket: &NewBucket) -> crate::Result<Self> {
        crate::runtime()?.block_on(Self::create(new_bucket))
    }

    /// Returns all `Bucket`s within this project.
    ///
    /// ### Note
    /// When using incorrect permissions, this function fails silently and returns an empty list.
    ///
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    ///
    /// let buckets = Bucket::list().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list() -> Result<Vec<Self>, Error> {
        let url = format!("{}/b/", crate::BASE_URL);
        let project = &crate::SERVICE_ACCOUNT.project_id;
        let query = [("project", project)];
        let result: GoogleResponse<ListResponse<Self>> = crate::CLIENT
            .get(&url)
            .headers(crate::get_headers().await?)
            .query(&query)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s.items),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::list`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn list_sync() -> Result<Vec<Self>, Error> {
        crate::runtime()?.block_on(Self::list())
    }

    /// Returns a single `Bucket` by its name. If the Bucket does not exist, an error is returned.
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    /// # use cloud_storage::bucket::NewBucket;
    /// # let new_bucket = NewBucket {
    /// #   name: "cloud-storage-rs-doc-2".to_string(),
    /// #    ..Default::default()
    /// # };
    /// # let _ = Bucket::create(&new_bucket).await?;
    ///
    /// let bucket = Bucket::read("cloud-storage-rs-doc-2").await?;
    /// # bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn read(name: &str) -> crate::Result<Self> {
        let url = format!("{}/b/{}", crate::BASE_URL, name);
        let result: GoogleResponse<Self> = crate::CLIENT
            .get(&url)
            .headers(crate::get_headers().await?)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::read`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn read_sync(name: &str) -> crate::Result<Self> {
        crate::runtime()?.block_on(Self::read(name))
    }

    /// Update an existing `Bucket`. If you declare you bucket as mutable, you can edit its fields.
    /// You can then flush your changes to Google Cloud Storage using this method.
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::bucket::{Bucket, RetentionPolicy};
    /// # use cloud_storage::bucket::NewBucket;
    /// # let new_bucket = NewBucket {
    /// #   name: "cloud-storage-rs-doc-3".to_string(),
    /// #    ..Default::default()
    /// # };
    /// # let _ = Bucket::create(&new_bucket).await?;
    ///
    /// let mut bucket = Bucket::read("cloud-storage-rs-doc-3").await?;
    /// bucket.retention_policy = Some(RetentionPolicy {
    ///     retention_period: 50,
    ///     effective_time: chrono::Utc::now() + chrono::Duration::seconds(50),
    ///     is_locked: Some(false),
    /// });
    /// bucket.update().await?;
    /// # bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn update(&self) -> crate::Result<Self> {
        let url = format!("{}/b/{}", crate::BASE_URL, self.name);
        let result: GoogleResponse<Self> = crate::CLIENT
            .put(&url)
            .headers(crate::get_headers().await?)
            .json(self)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::update`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn update_sync(&self) -> crate::Result<Self> {
        crate::runtime()?.block_on(self.update())
    }

    /// Delete an existing `Bucket`. This permanently removes a bucket from Google Cloud Storage.
    /// An error is returned when you don't have sufficient permissions, or when the
    /// `retention_policy` prevents you from deleting your Bucket.
    /// ### Example
    /// ```no_run
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    /// # use cloud_storage::bucket::NewBucket;
    /// # let new_bucket = NewBucket {
    /// #   name: "unnecessary-bucket".to_string(),
    /// #    ..Default::default()
    /// # };
    /// # let _ = Bucket::create(&new_bucket).await?;
    ///
    /// let bucket = Bucket::read("unnecessary-bucket").await?;
    /// bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete(self) -> crate::Result<()> {
        let url = format!("{}/b/{}", crate::BASE_URL, self.name);
        let response = crate::CLIENT
            .delete(&url)
            .headers(crate::get_headers().await?)
            .send()
            .await?;
        if response.status().is_success() {
            Ok(())
        } else {
            Err(Error::Google(response.json().await?))
        }
    }

    /// The synchronous equivalent of `Bucket::delete`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn delete_sync(self) -> crate::Result<()> {
        crate::runtime()?.block_on(self.delete())
    }

    /// Returns the [IAM Policy](https://cloud.google.com/iam/docs/) for this bucket.
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    /// # use cloud_storage::bucket::NewBucket;
    /// # let new_bucket = NewBucket {
    /// #   name: "cloud-storage-rs-doc-4".to_string(),
    /// #    ..Default::default()
    /// # };
    /// # let _ = Bucket::create(&new_bucket).await?;
    ///
    /// let bucket = Bucket::read("cloud-storage-rs-doc-4").await?;
    /// let policy = bucket.get_iam_policy().await?;
    /// # bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_iam_policy(&self) -> crate::Result<IamPolicy> {
        let url = format!("{}/b/{}/iam", crate::BASE_URL, self.name);
        let result: GoogleResponse<IamPolicy> = crate::CLIENT
            .get(&url)
            .headers(crate::get_headers().await?)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::get_iam_policy`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn get_iam_policy_sync(&self) -> crate::Result<IamPolicy> {
        crate::runtime()?.block_on(self.get_iam_policy())
    }

    /// Updates the [IAM Policy](https://cloud.google.com/iam/docs/) for this bucket.
    /// ### Example
    /// ```
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    /// use cloud_storage::bucket::{IamPolicy, Binding, IamRole, StandardIamRole, Entity};
    /// # use cloud_storage::bucket::NewBucket;
    /// # let new_bucket = NewBucket {
    /// #   name: "cloud-storage-rs-doc-5".to_string(),
    /// #    ..Default::default()
    /// # };
    /// # let _ = Bucket::create(&new_bucket).await?;
    ///
    /// let bucket = Bucket::read("cloud-storage-rs-doc-5").await?;
    /// let iam_policy = IamPolicy {
    ///     version: 1,
    ///     bindings: vec![
    ///         Binding {
    ///             role: IamRole::Standard(StandardIamRole::ObjectViewer),
    ///             members: vec!["allUsers".to_string()],
    ///             condition: None,
    ///         }
    ///     ],
    ///     ..Default::default()
    /// };
    /// let policy = bucket.set_iam_policy(&iam_policy).await?;
    /// # bucket.delete().await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn set_iam_policy(&self, iam: &IamPolicy) -> crate::Result<IamPolicy> {
        let url = format!("{}/b/{}/iam", crate::BASE_URL, self.name);
        let result: GoogleResponse<IamPolicy> = crate::CLIENT
            .put(&url)
            .headers(crate::get_headers().await?)
            .json(iam)
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::set_iam_policy`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn set_iam_policy_sync(&self, iam: &IamPolicy) -> crate::Result<IamPolicy> {
        crate::runtime()?.block_on(self.set_iam_policy(iam))
    }

    /// Checks whether the user provided in the service account has this permission.
    /// ### Example
    /// ```no_run
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use cloud_storage::Bucket;
    ///
    /// let bucket = Bucket::read("my-bucket").await?;
    /// bucket.test_iam_permission("storage.buckets.get").await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn test_iam_permission(&self, permission: &str) -> crate::Result<TestIamPermission> {
        if permission == "storage.buckets.list" || permission == "storage.buckets.create" {
            return Err(Error::new(
                "tested permission must not be `storage.buckets.list` or `storage.buckets.create`",
            ));
        }
        let url = format!("{}/b/{}/iam/testPermissions", crate::BASE_URL, self.name);
        let result: GoogleResponse<TestIamPermission> = crate::CLIENT
            .get(&url)
            .headers(crate::get_headers().await?)
            .query(&[("permissions", permission)])
            .send()
            .await?
            .json()
            .await?;
        match result {
            GoogleResponse::Success(s) => Ok(s),
            GoogleResponse::Error(e) => Err(e.into()),
        }
    }

    /// The synchronous equivalent of `Bucket::test_iam_policy`.
    ///
    /// ### Features
    /// This function requires that the feature flag `sync` is enabled in `Cargo.toml`.
    #[cfg(feature = "sync")]
    pub fn test_iam_permission_sync(&self, permission: &str) -> crate::Result<TestIamPermission> {
        crate::runtime()?.block_on(self.test_iam_permission(permission))
    }

    fn _lock_retention_policy() {
        todo!()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::resources::common::Role;

    #[tokio::test]
    async fn create() -> Result<(), Box<dyn std::error::Error>> {
        dotenv::dotenv().ok();
        let base_name = std::env::var("TEST_BUCKET")?;
        // use a more complex bucket in this test.
        let new_bucket = NewBucket {
            name: format!("{}-test-create", base_name),
            default_event_based_hold: Some(true),
            acl: Some(vec![NewBucketAccessControl {
                entity: Entity::AllUsers,
                role: Role::Reader,
            }]),
            default_object_acl: Some(vec![NewDefaultObjectAccessControl {
                entity: Entity::AllUsers,
                role: Role::Reader,
            }]),
            iam_configuration: Some(IamConfiguration {
                uniform_bucket_level_access: UniformBucketLevelAccess {
                    enabled: false,
                    locked_time: None,
                },
            }),
            ..Default::default()
        };
        let bucket = Bucket::create(&new_bucket).await?;
        bucket.delete().await?;
        Ok(())
    }

    #[tokio::test]
    async fn list() -> Result<(), Box<dyn std::error::Error>> {
        Bucket::list().await?;
        Ok(())
    }

    #[tokio::test]
    async fn read() -> Result<(), Box<dyn std::error::Error>> {
        let bucket = crate::create_test_bucket("test-read").await;
        let also_bucket = Bucket::read(&bucket.name).await?;
        assert_eq!(bucket, also_bucket);
        bucket.delete().await?;
        assert!(also_bucket.delete().await.is_err());
        Ok(())
    }

    #[tokio::test]
    async fn update() -> Result<(), Box<dyn std::error::Error>> {
        let mut bucket = crate::create_test_bucket("test-update").await;
        bucket.retention_policy = Some(RetentionPolicy {
            retention_period: 50,
            effective_time: chrono::Utc::now() + chrono::Duration::seconds(50),
            is_locked: Some(false),
        });
        bucket.update().await?;
        let updated = Bucket::read(&bucket.name).await?;
        assert_eq!(updated.retention_policy.unwrap().retention_period, 50);
        bucket.delete().await?;
        Ok(())
    }

    // used a lot throughout the other tests, but included for completeness
    #[tokio::test]
    async fn delete() -> Result<(), Box<dyn std::error::Error>> {
        let bucket = crate::create_test_bucket("test-delete").await;
        bucket.delete().await?;
        Ok(())
    }

    #[tokio::test]
    async fn get_iam_policy() -> Result<(), Box<dyn std::error::Error>> {
        let bucket = crate::create_test_bucket("test-get-iam-policy").await;
        bucket.get_iam_policy().await?;
        bucket.delete().await?;
        Ok(())
    }

    #[tokio::test]
    async fn set_iam_policy() -> Result<(), Box<dyn std::error::Error>> {
        let bucket = crate::create_test_bucket("test-set-iam-policy").await;
        let iam_policy = IamPolicy {
            bindings: vec![Binding {
                role: IamRole::Standard(StandardIamRole::ObjectViewer),
                members: vec!["allUsers".to_string()],
                condition: None,
            }],
            ..Default::default()
        };
        bucket.set_iam_policy(&iam_policy).await?;
        assert_eq!(bucket.get_iam_policy().await?.bindings, iam_policy.bindings);
        bucket.delete().await?;
        Ok(())
    }

    #[tokio::test]
    async fn test_iam_permission() -> Result<(), Box<dyn std::error::Error>> {
        let bucket = crate::create_test_bucket("test-test-ia-permission").await;
        bucket.test_iam_permission("storage.buckets.get").await?;
        bucket.delete().await?;
        Ok(())
    }

    #[cfg(feature = "sync")]
    mod sync {
        use super::*;
        use crate::resources::common::Role;

        #[test]
        fn create() -> Result<(), Box<dyn std::error::Error>> {
            dotenv::dotenv().ok();
            let base_name = std::env::var("TEST_BUCKET")?;
            // use a more complex bucket in this test.
            let new_bucket = NewBucket {
                name: format!("{}-test-create", base_name),
                default_event_based_hold: Some(true),
                acl: Some(vec![NewBucketAccessControl {
                    entity: Entity::AllUsers,
                    role: Role::Reader,
                }]),
                default_object_acl: Some(vec![NewDefaultObjectAccessControl {
                    entity: Entity::AllUsers,
                    role: Role::Reader,
                }]),
                iam_configuration: Some(IamConfiguration {
                    uniform_bucket_level_access: UniformBucketLevelAccess {
                        enabled: false,
                        locked_time: None,
                    },
                }),
                ..Default::default()
            };
            let bucket = Bucket::create_sync(&new_bucket)?;
            bucket.delete_sync()?;
            Ok(())
        }

        #[test]
        fn list() -> Result<(), Box<dyn std::error::Error>> {
            Bucket::list_sync()?;
            Ok(())
        }

        #[test]
        fn read() -> Result<(), Box<dyn std::error::Error>> {
            let bucket = crate::create_test_bucket_sync("test-read");
            let also_bucket = Bucket::read_sync(&bucket.name)?;
            assert_eq!(bucket, also_bucket);
            bucket.delete_sync()?;
            assert!(also_bucket.delete_sync().is_err());
            Ok(())
        }

        #[test]
        fn update() -> Result<(), Box<dyn std::error::Error>> {
            let mut bucket = crate::create_test_bucket_sync("test-update");
            bucket.retention_policy = Some(RetentionPolicy {
                retention_period: 50,
                effective_time: chrono::Utc::now() + chrono::Duration::seconds(50),
                is_locked: Some(false),
            });
            bucket.update_sync()?;
            let updated = Bucket::read_sync(&bucket.name)?;
            assert_eq!(updated.retention_policy.unwrap().retention_period, 50);
            bucket.delete_sync()?;
            Ok(())
        }

        // used a lot throughout the other tests, but included for completeness
        #[test]
        fn delete() -> Result<(), Box<dyn std::error::Error>> {
            let bucket = crate::create_test_bucket_sync("test-delete");
            bucket.delete_sync()?;
            Ok(())
        }

        #[test]
        fn get_iam_policy() -> Result<(), Box<dyn std::error::Error>> {
            let bucket = crate::create_test_bucket_sync("test-get-iam-policy");
            bucket.get_iam_policy_sync()?;
            bucket.delete_sync()?;
            Ok(())
        }

        #[test]
        fn set_iam_policy() -> Result<(), Box<dyn std::error::Error>> {
            // use crate::resources::iam_policy::{Binding, IamRole, StandardIamRole};

            let bucket = crate::create_test_bucket_sync("test-set-iam-policy");
            let iam_policy = IamPolicy {
                bindings: vec![Binding {
                    role: IamRole::Standard(StandardIamRole::ObjectViewer),
                    members: vec!["allUsers".to_string()],
                    condition: None,
                }],
                ..Default::default()
            };
            bucket.set_iam_policy_sync(&iam_policy)?;
            assert_eq!(bucket.get_iam_policy_sync()?.bindings, iam_policy.bindings);
            bucket.delete_sync()?;
            Ok(())
        }

        #[test]
        fn test_iam_permission() -> Result<(), Box<dyn std::error::Error>> {
            let bucket = crate::create_test_bucket_sync("test-test-ia-permission");
            bucket.test_iam_permission_sync("storage.buckets.get")?;
            bucket.delete_sync()?;
            Ok(())
        }
    }
}
