use std::collections::{BTreeMap, BTreeSet};

use quote::ToTokens as _;

use crate::generator::{
  ast::{
    ContentCategory, DeriveTrait, Documentation, EnumToken, EnumVariantToken, FieldDef, FieldNameToken, MethodKind,
    MethodNameToken, ResponseMediaType, ResponseStatusCategory, ResponseVariant, ResponseVariantCategory,
    StatusCodeToken, StatusHandler, StructDef, StructKind, StructMethod, StructToken, TypeRef, ValidationAttribute,
  },
  codegen::{Visibility, structs::StructFragment},
  converter::GenerationTarget,
};

fn base_struct(kind: StructKind) -> StructDef {
  StructDef {
    name: StructToken::new("Sample"),
    docs: Documentation::from_lines(["Sample struct"]),
    fields: vec![
      FieldDef::builder()
        .name(FieldNameToken::new("field"))
        .rust_type(TypeRef::new("String"))
        .validation_attrs(vec![ValidationAttribute::Length {
          min: Some(1),
          max: None,
        }])
        .build(),
    ],
    serde_attrs: vec![],
    outer_attrs: vec![],
    methods: vec![],
    kind,
    ..Default::default()
  }
}

fn make_response_parser_struct(variant: ResponseVariant) -> StructDef {
  let mut def = base_struct(StructKind::OperationRequest);
  let category = variant
    .media_types
    .first()
    .map_or(ContentCategory::Json, |m| m.category);
  let status_code = variant.status_code;

  def.methods.push(StructMethod {
    name: MethodNameToken::new("parse_response"),
    docs: Documentation::from_lines(["Parse response"]),
    kind: MethodKind::ParseResponse {
      response_enum: EnumToken::new("ResponseEnum"),
      status_handlers: vec![StatusHandler {
        status_code,
        dispatch: ResponseStatusCategory::Single(ResponseVariantCategory { category, variant }),
      }],
      default_handler: None,
    },
  });
  def
}

#[test]
fn generates_struct_with_supplied_derives() {
  let def = base_struct(StructKind::Schema);
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(code.contains("derive"), "missing derive attribute");
  assert!(code.contains("Debug"), "missing Debug derive");
  assert!(code.contains("Clone"), "missing Clone derive");
  assert!(code.contains("pub struct Sample"), "missing struct declaration");
}

#[test]
fn test_validation_attribute_generation() {
  let cases = [(true, true, "validation present"), (false, false, "validation absent")];
  for (has_validation, should_contain_validate, desc) in cases {
    let mut def = base_struct(StructKind::Schema);
    if !has_validation {
      def.fields[0].validation_attrs.clear();
    }
    let tokens =
      StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
    let code = tokens.to_string();
    assert_eq!(
      code.contains("validate"),
      should_contain_validate,
      "validation attribute mismatch for case: {desc}"
    );
  }
}

#[test]
fn renders_response_parser_method() {
  let def = make_response_parser_struct(
    ResponseVariant::builder()
      .status_code(StatusCodeToken::Ok200)
      .variant_name(EnumVariantToken::new("Ok"))
      .media_types(vec![ResponseMediaType::new("application/json")])
      .build(),
  );
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(code.contains("fn parse_response"), "missing parse_response method");
  assert!(code.contains("ResponseEnum"), "missing ResponseEnum type");
}

#[test]
fn test_text_response_parsing() {
  let cases = [
    (
      TypeRef::new("String"),
      "req . text () . await ?",
      "text/plain String response",
    ),
    (
      TypeRef::new("i32"),
      "req . text () . await ? . parse :: < i32 > () ?",
      "text/plain i32 response with parsing",
    ),
  ];
  for (st, expected_code, desc) in cases {
    let def = make_response_parser_struct(
      ResponseVariant::builder()
        .status_code(StatusCodeToken::Ok200)
        .variant_name(EnumVariantToken::new("Ok"))
        .media_types(vec![ResponseMediaType::with_schema("text/plain", Some(st.clone()))])
        .maybe_schema_type(Some(st))
        .build(),
    );
    let tokens =
      StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
    let code = tokens.to_string();
    assert!(code.contains(expected_code), "missing expected code for {desc}");
    assert!(
      code.contains("Ok (ResponseEnum :: Ok (data))"),
      "missing success return for {desc}"
    );
  }
}

#[test]
fn renders_json_parser_for_custom_struct() {
  let def = make_response_parser_struct(
    ResponseVariant::builder()
      .status_code(StatusCodeToken::Ok200)
      .variant_name(EnumVariantToken::new("Ok"))
      .media_types(vec![ResponseMediaType::with_schema(
        "application/json",
        Some(TypeRef::new("MyStruct")),
      )])
      .schema_type(TypeRef::new("MyStruct"))
      .build(),
  );
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(
    code.contains("json_with_diagnostics"),
    "missing json_with_diagnostics call"
  );
  assert!(code.contains("MyStruct"), "missing MyStruct type");
}

#[test]
fn test_binary_response_parsing() {
  let def = make_response_parser_struct(
    ResponseVariant::builder()
      .status_code(StatusCodeToken::Ok200)
      .variant_name(EnumVariantToken::new("Ok"))
      .media_types(vec![ResponseMediaType::with_schema(
        "application/octet-stream",
        Some(TypeRef::new("Vec<u8>")),
      )])
      .schema_type(TypeRef::new("Vec<u8>"))
      .build(),
  );
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(
    code.contains("req . bytes () . await ? . to_vec ()"),
    "missing bytes conversion for binary content"
  );
}

#[test]
fn test_binary_content_type_with_json_schema_uses_json_parsing() {
  let def = make_response_parser_struct(
    ResponseVariant::builder()
      .status_code(StatusCodeToken::ClientError4XX)
      .variant_name(EnumVariantToken::new("ClientError"))
      .media_types(vec![ResponseMediaType::with_schema(
        "application/octet-stream",
        Some(TypeRef::new("ErrorResponse")),
      )])
      .schema_type(TypeRef::new("ErrorResponse"))
      .build(),
  );
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(
    code.contains("Diagnostics :: < ErrorResponse > :: json_with_diagnostics"),
    "binary content-type with JSON schema should use JSON parsing: {code}"
  );
  assert!(
    !code.contains("to_vec ()"),
    "should NOT use bytes to_vec() extraction for JSON schema type: {code}"
  );
}

#[test]
fn test_event_stream_response_generates_from_response() {
  let def = make_response_parser_struct(
    ResponseVariant::builder()
      .status_code(StatusCodeToken::Ok200)
      .variant_name(EnumVariantToken::new("Ok"))
      .media_types(vec![ResponseMediaType::with_schema(
        "text/event-stream",
        Some(TypeRef::new("StreamEvent")),
      )])
      .schema_type(TypeRef::new("oas3_gen_support::EventStream<StreamEvent>"))
      .build(),
  );
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(
    code.contains("from_response"),
    "EventStream response should call from_response: {code}"
  );
}

#[test]
fn test_struct_generates_debug_and_clone() {
  let def = base_struct(StructKind::Schema);
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();
  assert!(code.contains("Debug"), "missing Debug derive");
  assert!(code.contains("Clone"), "missing Clone derive");
}

#[test]
fn test_header_params_struct_generates_try_from_header_map() {
  let def = StructDef {
    name: StructToken::new("RequestHeader"),
    docs: Documentation::from_lines(["Header parameters"]),
    fields: vec![
      FieldDef::builder()
        .name(FieldNameToken::new("x_api_key"))
        .rust_type(TypeRef::new("String"))
        .original_name("X-Api-Key")
        .build(),
      FieldDef::builder()
        .name(FieldNameToken::new("x_request_id"))
        .rust_type(TypeRef::new("String").with_option())
        .original_name("X-Request-ID")
        .build(),
    ],
    kind: StructKind::HeaderParams,
    ..Default::default()
  };

  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();

  assert!(
    code.contains("impl core :: convert :: TryFrom < & RequestHeader > for http :: HeaderMap"),
    "missing TryFrom impl for reference header: {code}"
  );
  assert!(
    code.contains("impl core :: convert :: TryFrom < RequestHeader > for http :: HeaderMap"),
    "missing TryFrom impl for owned header: {code}"
  );
  assert!(
    code.contains("type Error = http :: header :: InvalidHeaderValue"),
    "missing Error type: {code}"
  );
  assert!(code.contains("X_API_KEY"), "missing X_API_KEY constant: {code}");
  assert!(code.contains("X_REQUEST_ID"), "missing X_REQUEST_ID constant: {code}");
  assert!(
    code.contains("if let Some (value) = & headers . x_request_id"),
    "missing optional field handling: {code}"
  );
}

#[test]
fn test_non_header_params_struct_does_not_generate_try_from_header_map() {
  let def = base_struct(StructKind::Schema);
  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();

  assert!(
    !code.contains("impl TryFrom"),
    "Schema struct should not have TryFrom impl: {code}"
  );
}

#[test]
fn test_header_params_with_primitive_types() {
  use crate::generator::ast::RustPrimitive;

  let def = StructDef {
    name: StructToken::new("IntHeader"),
    docs: Documentation::default(),
    fields: vec![
      FieldDef::builder()
        .name(FieldNameToken::new("x_count"))
        .rust_type(TypeRef::new(RustPrimitive::I32))
        .original_name("X-Count")
        .build(),
    ],
    kind: StructKind::HeaderParams,
    ..Default::default()
  };

  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();

  assert!(
    code.contains("to_string ()"),
    "primitive types should use to_string() conversion: {code}"
  );
}

#[test]
fn test_builder_renames_reserved_bon_field_names() {
  let cases = [
    (
      "build",
      true,
      "field named 'build' should get #[builder(name = build_value)]",
    ),
    (
      "builder",
      true,
      "field named 'builder' should get #[builder(name = builder_value)]",
    ),
    ("name", false, "field named 'name' should not be renamed"),
  ];
  for (field_name, should_rename, desc) in cases {
    let mut field = FieldDef::builder()
      .name(FieldNameToken::new(field_name))
      .rust_type(TypeRef::new("String").with_option())
      .build();
    field = field.with_builder_attrs();

    let def = StructDef {
      name: StructToken::new("TestStruct"),
      docs: Documentation::default(),
      fields: vec![field],
      kind: StructKind::Schema,
      additional_derives: BTreeSet::from([DeriveTrait::Builder]),
      ..Default::default()
    };

    let tokens =
      StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
    let code = tokens.to_string();
    let expected_rename = format!("{field_name}_value");

    assert_eq!(code.contains(&expected_rename), should_rename, "{desc}: {code}");
  }
}

#[test]
fn test_builder_skipped_field_named_build_not_renamed() {
  let mut field = FieldDef::builder()
    .name(FieldNameToken::new("build"))
    .rust_type(TypeRef::new("String").with_option())
    .doc_hidden(true)
    .default_value(serde_json::Value::String("hidden".to_string()))
    .build();
  field = field.with_builder_attrs();

  let def = StructDef {
    name: StructToken::new("TestStruct"),
    docs: Documentation::default(),
    fields: vec![field],
    kind: StructKind::Schema,
    additional_derives: BTreeSet::from([DeriveTrait::Builder]),
    ..Default::default()
  };

  let tokens =
    StructFragment::new(def, BTreeMap::new(), Visibility::Public, GenerationTarget::Client).into_token_stream();
  let code = tokens.to_string();

  assert!(code.contains("builder (skip"), "hidden field should be skipped: {code}");
  assert!(
    !code.contains("build_value"),
    "skipped field should not be renamed: {code}"
  );
}
