module CorreiosApi
  class Client < Ac::Base
    BASE_URL = "https://api.correios.com.br"
    MAX_RETRIES = 2

    SERVICES = [
      {
        code: "03220",
        name: "SEDEX CONTRATO AG"
      },
      {
        code: "03298",
        name: "PAC CONTRATO AG"
      },
      {
        code: "04227",
        name: "CORREIOS MINI ENVIOS CTR AG"
      }
    ]
    SERVICE_NAMES = {
      "03220" => "SEDEX CONTRATO AG",
      "03298" => "PAC CONTRATO AG",
      "04227" => "CORREIOS MINI ENVIOS CTR AG"
    }

    attr_reader :username, :password, :post_card, :contract_number, :token, :expires_in
    def initialize username:, password:, post_card:, contract_number: nil, token: nil, expires_in: nil
      @username = username
      @password = password
      @post_card = post_card
      @contract_number = contract_number
      @token = token
      # if @token.nil? || @expires_in < Time.now
      #   authenticate
      # end
    end

    def authenticate
      body = {
        numero: @post_card
      }
      response = post(
        "/token/v1/autentica/cartaopostagem",
        headers: {
          "Authorization" => "Basic #{Base64.strict_encode64("#{@username}:#{@password}")}",
          "Content-Type" => "application/json"
        },
        body: JSON.dump(body)
      ) { |response| validate_response(response, "token") }
      @token = response.json.dig("token")
      @expires_in = Time.new(response.json.dig("expiraEm"))
    end

    def tracking_package tracking_code, result_type = "T"
      response = get("/srorastro/v1/objetos/#{tracking_code}?resultado=#{result_type}")
      Tracking.new(response.json)
    end

    def create_pre_shipping shipper_name:, shipper_tax_id:, shipper_zipcode:, shipper_street:, shipper_address_number:, shipper_district:, shipper_city:, shipper_state_code:, receiver_name:, receiver_tax_id:, receiver_zipcode:, receiver_street:, receiver_address_number:, receiver_district:, receiver_city:, receiver_state_code:, service_code:, price:, items:, weight_g:, height_cm: 15, width_cm: 15, length_cm: 15
      body = {
        idCorreios: @username,
        remetente: {
          nome: shipper_name,
          cpfCnpj: shipper_tax_id,
          endereco: {
            cep: shipper_zipcode,
            logradouro: shipper_street,
            numero: shipper_address_number,
            bairro: shipper_district,
            cidade: shipper_city,
            uf: shipper_state_code
          }
        },
        destinatario: {
          nome: receiver_name,
          cpfCnpj: receiver_tax_id,
          endereco: {
            cep: receiver_zipcode,
            logradouro: receiver_street,
            numero: receiver_address_number,
            bairro: receiver_district,
            cidade: receiver_city,
            uf: receiver_state_code
          }
        },
        codigoServico: service_code,
        precoPrePostagem: price,
        numeroCartaoPostagem: @post_card,
        itensDeclaracaoConteudo: items, # conteudo, quantidade, valor
        pesoInformado: weight_g,
        codigoFormatoObjetoInformado: "2",
        alturaInformada: height_cm,
        larguraInformada: width_cm,
        comprimentoInformado: length_cm,
        cienteObjetoNaoProibido: "1",
        modalidadePagamento: "2" # 1-à vista, 2-à faturar, 3- à avista e à afaturar e 4- Prestação de contas recebimento ou pagamento
        # solicitarColeta: "N",
        # dataPrevistaPostagem: "29/09/2024"
      }

      response = post(
        "/prepostagem/v1/prepostagens",
        headers: headers,
        body: JSON.dump(body)
      ) { |response| validate_response(response, "id") }
      PreShipping.new(response.json)
    end

    def create_label pre_shipping
      body = {
        codigosObjeto: [
          pre_shipping.tracking_code
        ],
        idCorreios: @username,
        numeroCartaoPostagem: @post_card,
        tipoRotulo: "P", # P (padrão) ou R (reduzido)
        formatoRotulo: "ET", # ET (Etiqueta) ou EV (Envelope)
        imprimeRemetente: "S", # S (Sim) ou N (não)
        layoutImpressao: "PADRAO" # PADRAO, LINEAR_100_150, LINEAR_100_80, LINEAR_A4, LINEAR_A
      }

      response = post(
        "/prepostagem/v1/prepostagens/rotulo/assincrono/pdf",
        headers: headers,
        body: JSON.dump(body)
      ) { |response| validate_response(response, "idRecibo") }
      sleep(0.5) # Tem que esperar o PDF ficar pronto
      get_label(response.json.dig("idRecibo"))
    end

    def get_label id
      response = get(
        "/prepostagem/v1/prepostagens/rotulo/download/assincrono/#{id}",
        headers: headers
      ) { |response| validate_response(response, "nome") }
      Label.new(response.json)
    end

    def create_rates zipcode_from:, zipcode_to:, weight_g: 1000, height_cm: 15, width_cm: 15, length_cm: 15
      services_with_time = delivery_time(zipcode_from, zipcode_to)
      services_with_cost = delivery_cost(zipcode_from, zipcode_to, weight_g, height_cm, width_cm, length_cm)

      rates = services_with_time.map do |service_time|
        service_cost = services_with_cost.find { |service| service["coProduto"] == service_time["coProduto"] }
        Rate.new(
          service_name: SERVICE_NAMES[service_time["coProduto"]],
          service_code: service_time["coProduto"],
          price: service_cost["pcFinal"],
          delivery_time_in_days: service_time["prazoEntrega"],
          max_delivery_time: service_time["dataMaxima"],
          message: service_time["msgPrazo"],
          errors: [
            service_cost.dig("txErro"),
            service_time.dig("txErro")
          ].compact
        )
      end
      ShippingQuotes.new(rates)
    end

    def delivery_time zipcode_from, zipcode_to
      zipcode_from_digits = CodigoPostal.new(zipcode_from).cep_digits
      zipcode_to_digits = CodigoPostal.new(zipcode_to).cep_digits
      body = {
        idLote: "1",
        parametrosPrazo: SERVICES.map { |service| {coProduto: service[:code], nuRequisicao: 1, cepOrigem: zipcode_from_digits, cepDestino: zipcode_to_digits} }
      }
      response = post(
        "/prazo/v1/nacional",
        headers: headers,
        body: JSON.dump(body)
      ) { |response| validate_response(response, "coProduto") }
      response.json
    end

    def delivery_cost zipcode_from, zipcode_to, weight_g, height_cm, width_cm, length_cm
      zipcode_from_digits = CodigoPostal.new(zipcode_from).cep_digits
      zipcode_to_digits = CodigoPostal.new(zipcode_to).cep_digits
      params_produto = SERVICES.map do |service|
        {
          coProduto: service[:code],
          nuRequisicao: "1",
          cepOrigem: zipcode_from_digits,
          cepDestino: zipcode_to_digits,
          psObjeto: weight_g,
          altura: height_cm,
          largura: width_cm,
          comprimento: length_cm,
          tpObjeto: "2"
        }
      end
      body = {
        idLote: "1",
        parametrosProduto: params_produto
      }
      response = post(
        "/preco/v1/nacional",
        headers: headers,
        body: JSON.dump(body)
      ) { |response| validate_response(response, "coProduto") }
      response.json
    end

    private

    def headers
      {
        "Authorization" => "Bearer #{@token}",
        "Content-Type" => "application/json"
      }
    end

    def validate_response(response, required_response_key)
      puts response.json unless response.success?
      ![500, 429].include?(response.code) || response.json.dig(*required_response_key)
    end
  end
end